64bit compatibility fixes
[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     i->swf->fileAttributes = 9; // as3, local-with-network
1110     
1111     i->swf->firstTag = swf_InsertTag(NULL,ST_SETBACKGROUNDCOLOR);
1112     i->tag = i->swf->firstTag;
1113     RGBA rgb;
1114     rgb.a = rgb.r = rgb.g = rgb.b = 0xff;
1115     //rgb.r = 0;
1116     swf_SetRGB(i->tag,&rgb);
1117
1118     i->startdepth = i->depth = 0;
1119     i->startids = i->currentswfid = 0;
1120 }
1121
1122 static void startshape(gfxdevice_t*dev)
1123 {
1124     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1125     SRECT r;
1126
1127     if(i->shapeid>=0)
1128         return;
1129     //if(i->chardatapos && i->chardata[i->chardatapos-1].color.a)
1130     endtext(dev);
1131
1132     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
1133
1134     swf_ShapeNew(&i->shape);
1135     i->linestyleid = swf_ShapeAddLineStyle(i->shape,i->linewidth,&i->strokergb);
1136     i->fillstyleid = swf_ShapeAddSolidFillStyle(i->shape,&i->fillrgb);
1137     if(i->mark) {
1138         RGBA markcol = {0,i->mark[0],i->mark[1],i->mark[2]};
1139         swf_ShapeAddSolidFillStyle(i->shape,&markcol);
1140     }
1141
1142     i->shapeid = getNewID(dev);
1143     
1144     msg("<debug> Using shape id %d", i->shapeid);
1145
1146     swf_SetU16(i->tag,i->shapeid);  // ID
1147
1148     i->bboxrectpos = i->tag->len;
1149     /* changed later */
1150     swf_SetRect(i->tag,&i->pagebbox);
1151    
1152     memset(&i->bboxrect, 0, sizeof(i->bboxrect));
1153
1154     swf_SetShapeStyles(i->tag,i->shape);
1155     swf_ShapeCountBits(i->shape,NULL,NULL);
1156     swf_SetShapeBits(i->tag,i->shape);
1157
1158     /* TODO: do we really need this? */
1159     //swf_ShapeSetAll(i->tag,i->shape,/*x*/0,/*y*/0,i->linestyleid,0,0);
1160     //swf_ShapeSetAll(i->tag,i->shape,/*x*/UNDEFINED_COORD,/*y*/UNDEFINED_COORD,i->linestyleid,0,0);
1161     i->swflastx=i->swflasty=UNDEFINED_COORD;
1162     i->lastwasfill = -1;
1163     i->shapeisempty = 1;
1164 }
1165
1166 static void starttext(gfxdevice_t*dev)
1167 {
1168     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1169     if(i->shapeid>=0)
1170         endshape(dev);
1171
1172     if(i->config_watermark) {
1173         insert_watermark(dev, 0);
1174     }
1175     i->textid = getNewID(dev);
1176     i->swflastx=i->swflasty=0;
1177 }
1178             
1179
1180 /* TODO: move to ../lib/rfxswf */
1181 void changeRect(gfxdevice_t*dev, TAG*tag, int pos, SRECT*newrect)
1182 {
1183     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1184     /* determine length of old rect */
1185     tag->pos = pos;
1186     tag->readBit = 0;
1187     SRECT old;
1188     swf_GetRect(tag, &old);
1189     swf_ResetReadBits(tag);
1190     int pos_end = tag->pos;
1191
1192     int len = tag->len - pos_end;
1193     U8*data = (U8*)malloc(len);
1194     memcpy(data, &tag->data[pos_end], len);
1195     tag->writeBit = 0;
1196     tag->len = pos;
1197     swf_SetRect(tag, newrect);
1198     swf_SetBlock(tag, data, len);
1199     free(data);
1200     tag->pos = tag->readBit = 0;
1201 }
1202
1203 void cancelshape(gfxdevice_t*dev)
1204 {
1205     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1206     /* delete old shape tag */
1207     TAG*todel = i->tag;
1208     i->tag = i->tag->prev;
1209     swf_DeleteTag(0, todel);
1210     if(i->shape) {swf_ShapeFree(i->shape);i->shape=0;}
1211     i->shapeid = -1;
1212     i->bboxrectpos = -1;
1213
1214 //    i->currentswfid--; // doesn't work, for some reason
1215 }
1216
1217 void fixAreas(gfxdevice_t*dev)
1218 {
1219     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1220     if(!i->shapeisempty && i->fill &&
1221        (i->bboxrect.xmin == i->bboxrect.xmax ||
1222         i->bboxrect.ymin == i->bboxrect.ymax) &&
1223         i->config_minlinewidth >= 0.001
1224        ) {
1225         msg("<debug> Shape has size 0: width=%.2f height=%.2f",
1226                 (i->bboxrect.xmax-i->bboxrect.xmin)/20.0,
1227                 (i->bboxrect.ymax-i->bboxrect.ymin)/20.0
1228                 );
1229     
1230         SRECT r = i->bboxrect;
1231         
1232         if(r.xmin == r.xmax && r.ymin == r.ymax) {
1233             /* this thing comes down to a single dot- nothing to fix here */
1234             return;
1235         }
1236
1237         cancelshape(dev);
1238
1239         RGBA save_col = i->strokergb;
1240         int  save_width = i->linewidth;
1241
1242         i->strokergb = i->fillrgb;
1243         i->linewidth = (int)(i->config_minlinewidth*20);
1244         if(i->linewidth==0) i->linewidth = 1;
1245         
1246         startshape(dev);
1247         stopFill(dev);
1248
1249         moveto(dev, i->tag, r.xmin/20.0,r.ymin/20.0);
1250         lineto(dev, i->tag, r.xmax/20.0,r.ymax/20.0);
1251
1252         i->strokergb = save_col;
1253         i->linewidth = save_width;
1254     }
1255     
1256 }
1257
1258 static void endshape_noput(gfxdevice_t*dev)
1259 {
1260     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1261     if(i->shapeid<0) 
1262         return;
1263     //changeRect(dev, i->tag, i->bboxrectpos, &i->bboxrect);
1264     i->shapeid = -1;
1265     if(i->shape) {
1266         swf_ShapeFree(i->shape);
1267         i->shape=0;
1268     }
1269     i->fill=0;
1270     i->shapeposx=0;
1271     i->shapeposy=0;
1272 }
1273
1274 static void endshape(gfxdevice_t*dev)
1275 {
1276     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1277     if(i->shapeid<0) 
1278         return;
1279
1280     fixAreas(dev);
1281         
1282     if(i->shapeisempty ||
1283        /*bbox empty?*/
1284        (i->bboxrect.xmin == i->bboxrect.xmax && 
1285         i->bboxrect.ymin == i->bboxrect.ymax))
1286     {
1287         // delete the shape again, we didn't do anything
1288         msg("<debug> cancelling shape: bbox is (%f,%f,%f,%f)",
1289                 i->bboxrect.xmin /20.0,
1290                 i->bboxrect.ymin /20.0,
1291                 i->bboxrect.xmax /20.0,
1292                 i->bboxrect.ymax /20.0
1293                 );
1294         cancelshape(dev);
1295         return;
1296     }
1297     
1298     swf_ShapeSetEnd(i->tag);
1299
1300     SRECT r = swf_ClipRect(i->pagebbox, i->bboxrect);
1301     changeRect(dev, i->tag, i->bboxrectpos, &r);
1302
1303     msg("<trace> Placing shape ID %d", i->shapeid);
1304
1305     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
1306     MATRIX m = i->page_matrix;
1307     m.tx += i->shapeposx;
1308     m.ty += i->shapeposy;
1309     swf_ObjectPlace(i->tag,i->shapeid,getNewDepth(dev),&m,NULL,NULL);
1310
1311     if(i->config_animate) {
1312         i->tag = swf_InsertTag(i->tag,ST_SHOWFRAME);
1313     }
1314
1315     swf_ShapeFree(i->shape);
1316     i->shape = 0;
1317     i->shapeid = -1;
1318     i->bboxrectpos = -1;
1319
1320     i->fill=0;
1321     i->shapeposx=0;
1322     i->shapeposy=0;
1323 }
1324
1325 void wipeSWF(SWF*swf)
1326 {
1327     TAG*tag = swf->firstTag;
1328     while(tag) {
1329         TAG*next = tag->next;
1330         if(tag->id != ST_SETBACKGROUNDCOLOR &&
1331            tag->id != ST_END &&
1332            tag->id != ST_DOACTION &&
1333            tag->id != ST_SHOWFRAME) {
1334             swf_DeleteTag(swf, tag);
1335         }
1336         tag = next;
1337     }
1338 }
1339
1340 void swfoutput_finalize(gfxdevice_t*dev)
1341 {
1342     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1343
1344     if(i->tag && i->tag->id == ST_END)
1345         return; //already done
1346
1347     i->swf->fileVersion = i->config_flashversion;
1348     i->swf->frameRate = i->config_framerate*0x100;
1349
1350     if(i->config_bboxvars) {
1351         TAG* tag = swf_InsertTag(i->swf->firstTag, ST_DOACTION);
1352         ActionTAG*a = 0;
1353         a = action_PushString(a, "xmin");
1354         a = action_PushFloat(a, i->swf->movieSize.xmin / 20.0);
1355         a = action_SetVariable(a);
1356         a = action_PushString(a, "ymin");
1357         a = action_PushFloat(a, i->swf->movieSize.ymin / 20.0);
1358         a = action_SetVariable(a);
1359         a = action_PushString(a, "xmax");
1360         a = action_PushFloat(a, i->swf->movieSize.xmax / 20.0);
1361         a = action_SetVariable(a);
1362         a = action_PushString(a, "ymax");
1363         a = action_PushFloat(a, i->swf->movieSize.ymax / 20.0);
1364         a = action_SetVariable(a);
1365         a = action_PushString(a, "width");
1366         a = action_PushFloat(a, (i->swf->movieSize.xmax - i->swf->movieSize.xmin) / 20.0);
1367         a = action_SetVariable(a);
1368         a = action_PushString(a, "height");
1369         a = action_PushFloat(a, (i->swf->movieSize.ymax - i->swf->movieSize.ymin) / 20.0);
1370         a = action_SetVariable(a);
1371         a = action_End(a);
1372         swf_ActionSet(tag, a);
1373         swf_ActionFree(a);
1374     }
1375
1376     if(i->mark) {
1377         free(i->mark);i->mark = 0;
1378     }
1379
1380     endpage(dev);
1381     fontlist_t *iterator = i->fontlist;
1382     while(iterator) {
1383         TAG*mtag = i->swf->firstTag;
1384         if(iterator->swffont) {
1385             if(!i->config_storeallcharacters) {
1386                 msg("<debug> Reducing font %s", iterator->swffont->name);
1387                 swf_FontReduce(iterator->swffont);
1388             }
1389             int used = iterator->swffont->use && iterator->swffont->use->used_glyphs;
1390             if(used) {
1391                 mtag = swf_InsertTag(mtag, ST_DEFINEFONT2);
1392                 swf_FontSetDefine2(mtag, iterator->swffont);
1393             }
1394         }
1395
1396         iterator = iterator->next;
1397     }
1398         
1399     i->tag = swf_InsertTag(i->tag,ST_END);
1400     TAG* tag = i->tag->prev;
1401
1402     /* remove the removeobject2 tags between the last ST_SHOWFRAME
1403        and the ST_END- they confuse the flash player  */
1404     while(tag->id == ST_REMOVEOBJECT2) {
1405         TAG* prev = tag->prev;
1406         swf_DeleteTag(i->swf, tag);
1407         tag = prev;
1408     }
1409     
1410     if(i->overflow) {
1411         wipeSWF(i->swf);
1412     }
1413     if(i->config_enablezlib || i->config_flashversion>=6) {
1414         i->swf->compressed = 1;
1415     }
1416
1417     /* Add AVM2 actionscript */
1418     if(i->config_flashversion>=9 && 
1419             (i->config_insertstoptag || i->hasbuttons)) {
1420         swf_AddButtonLinks(i->swf, i->config_insertstoptag, 
1421                 i->config_internallinkfunction||i->config_externallinkfunction);
1422     }
1423 //    if(i->config_reordertags)
1424 //      swf_Optimize(i->swf);
1425 }
1426
1427 int swfresult_save(gfxresult_t*gfx, const char*filename)
1428 {
1429     SWF*swf = (SWF*)gfx->internal;
1430     int fi;
1431     if(filename)
1432      fi = open(filename, O_BINARY|O_CREAT|O_TRUNC|O_WRONLY, 0777);
1433     else
1434      fi = 1; // stdout
1435     
1436     if(fi<=0) {
1437         msg("<fatal> Could not create \"%s\". ", FIXNULL(filename));
1438         return -1;
1439     }
1440     
1441     if FAILED(swf_WriteSWF(fi,swf)) 
1442         msg("<error> WriteSWF() failed.\n");
1443
1444     if(filename)
1445      close(fi);
1446     return 0;
1447 }
1448 void* swfresult_get(gfxresult_t*gfx, const char*name)
1449 {
1450     SWF*swf = (SWF*)gfx->internal;
1451     if(!strcmp(name, "swf")) {
1452         return (void*)swf_CopySWF(swf);
1453     } else if(!strcmp(name, "xmin")) {
1454         return (void*)(ptroff_t)(swf->movieSize.xmin/20);
1455     } else if(!strcmp(name, "ymin")) {
1456         return (void*)(ptroff_t)(swf->movieSize.ymin/20);
1457     } else if(!strcmp(name, "xmax")) {
1458         return (void*)(ptroff_t)(swf->movieSize.xmax/20);
1459     } else if(!strcmp(name, "ymax")) {
1460         return (void*)(ptroff_t)(swf->movieSize.ymax/20);
1461     } else if(!strcmp(name, "width")) {
1462         return (void*)(ptroff_t)((swf->movieSize.xmax - swf->movieSize.xmin)/20);
1463     } else if(!strcmp(name, "height")) {
1464         return (void*)(ptroff_t)((swf->movieSize.ymax - swf->movieSize.ymin)/20);
1465     }
1466     return 0;
1467 }
1468 void swfresult_destroy(gfxresult_t*gfx)
1469 {
1470     if(gfx->internal) {
1471         swf_FreeTags((SWF*)gfx->internal);
1472         free(gfx->internal);
1473         gfx->internal = 0;
1474     }
1475     memset(gfx, 0, sizeof(gfxresult_t));
1476     free(gfx);
1477 }
1478
1479 static void swfoutput_destroy(gfxdevice_t* dev);
1480
1481 gfxresult_t* swf_finish(gfxdevice_t* dev)
1482 {
1483     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1484     gfxresult_t*result;
1485
1486     if(i->config_linktarget) {
1487         free(i->config_linktarget);
1488         i->config_linktarget = 0;
1489     }
1490
1491     swfoutput_finalize(dev);
1492     SWF* swf = i->swf;i->swf = 0;
1493     swfoutput_destroy(dev);
1494
1495     result = (gfxresult_t*)rfx_calloc(sizeof(gfxresult_t));
1496     result->internal = swf;
1497     result->save = swfresult_save;
1498     result->write = 0;
1499     result->get = swfresult_get;
1500     result->destroy = swfresult_destroy;
1501     return result;
1502 }
1503
1504 /* Perform cleaning up */
1505 static void swfoutput_destroy(gfxdevice_t* dev) 
1506 {
1507     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1508     if(!i) {
1509         /* not initialized yet- nothing to destroy */
1510         return;
1511     }
1512
1513     fontlist_t *tmp,*iterator = i->fontlist;
1514     while(iterator) {
1515         if(iterator->swffont) {
1516             swf_FontFree(iterator->swffont);iterator->swffont=0;
1517         }
1518         tmp = iterator;
1519         iterator = iterator->next;
1520         free(tmp);
1521     }
1522     if(i->swf) {swf_FreeTags(i->swf);free(i->swf);i->swf = 0;}
1523
1524     free(i);i=0;
1525     memset(dev, 0, sizeof(gfxdevice_t));
1526 }
1527
1528 static void swfoutput_setstrokecolor(gfxdevice_t* dev, U8 r, U8 g, U8 b, U8 a)
1529 {
1530     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1531     if(i->strokergb.r == r &&
1532        i->strokergb.g == g &&
1533        i->strokergb.b == b &&
1534        i->strokergb.a == a) return;
1535
1536     if(i->shapeid>=0)
1537      endshape(dev);
1538     i->strokergb.r = r;
1539     i->strokergb.g = g;
1540     i->strokergb.b = b;
1541     i->strokergb.a = a;
1542 }
1543
1544 //#define ROUND_UP 19
1545 //#define ROUND_UP 10
1546
1547 static void swfoutput_setlinewidth(gfxdevice_t*dev, double _linewidth)
1548 {
1549     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1550     if(i->linewidth == (U16)(_linewidth*20+19.0/20.0))
1551         return;
1552     if(i->shapeid>=0)
1553         endshape(dev);
1554     i->linewidth = (U16)(_linewidth*20+19.0/20.0);
1555 }
1556
1557
1558 static void drawlink(gfxdevice_t*dev, ActionTAG*,ActionTAG*, gfxline_t*points, char mouseover, const char*url);
1559 static void swfoutput_namedlink(gfxdevice_t*dev, char*name, gfxline_t*points);
1560 static void swfoutput_linktopage(gfxdevice_t*dev, int page, gfxline_t*points);
1561 static void swfoutput_linktourl(gfxdevice_t*dev, const char*url, gfxline_t*points);
1562
1563 /*void swfoutput_drawlink(gfxdevice_t*dev, char*url, gfxline_t*points)
1564 {
1565     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1566     dev->drawlink(dev, points, url);
1567 }*/
1568
1569 void swf_drawlink(gfxdevice_t*dev, gfxline_t*points, const char*url)
1570 {
1571     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1572
1573     if(i->config_disablelinks)
1574         return;
1575
1576     if(!strncmp("http://pdf2swf:", url, 15)) {
1577         char*tmp = strdup(url);
1578         int l = strlen(tmp);
1579         if(tmp[l-1] == '/')
1580            tmp[l-1] = 0;
1581         swfoutput_namedlink(dev, tmp+15, points);
1582         free(tmp);
1583         return;
1584     } else if(!strncmp("page", url, 4)) {
1585         int t, nodigit=0;
1586         for(t=4;url[t];t++)
1587             if(url[t]<'0' || url[t]>'9')
1588                 nodigit = 1;
1589         if(!nodigit) {
1590             int page = atoi(&url[4]);
1591             if(page<0) page = 0;
1592             swfoutput_linktopage(dev, page, points);
1593         }
1594     } else {
1595         swfoutput_linktourl(dev, url, points);
1596     }
1597 }
1598 void swfoutput_linktourl(gfxdevice_t*dev, const char*url, gfxline_t*points)
1599 {
1600     ActionTAG* actions = 0;
1601     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1602     if(i->shapeid>=0)
1603         endshape(dev);
1604     if(i->textid>=0)
1605         endtext(dev);
1606
1607     /* TODO: escape special characters in url */
1608     
1609     if(i->config_externallinkfunction && i->config_flashversion<=8) {
1610         actions = action_PushString(actions, url); //parameter
1611         actions = action_PushInt(actions, 1); //number of parameters (1)
1612         actions = action_PushString(actions, i->config_externallinkfunction); //function name
1613         actions = action_CallFunction(actions);
1614     } else if(!i->config_linktarget) {
1615         if(!i->config_opennewwindow)
1616           actions = action_GetUrl(actions, url, "_parent");
1617         else
1618           actions = action_GetUrl(actions, url, "_this");
1619     } else {
1620         actions = action_GetUrl(actions, url, i->config_linktarget);
1621     }
1622     actions = action_End(actions);
1623    
1624     drawlink(dev, actions, 0, points, 0, url);
1625
1626     swf_ActionFree(actions);
1627 }
1628 void swfoutput_linktopage(gfxdevice_t*dev, int page, gfxline_t*points)
1629 {
1630     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1631     ActionTAG* actions = 0;
1632
1633     if(i->shapeid>=0)
1634         endshape(dev);
1635     if(i->textid>=0)
1636         endtext(dev);
1637   
1638     if(!i->config_internallinkfunction || i->config_flashversion>=9) {
1639         actions = action_GotoFrame(actions, page-1);
1640         actions = action_End(actions);
1641     } else {
1642         actions = action_PushInt(actions, page); //parameter
1643         actions = action_PushInt(actions, 1); //number of parameters (1)
1644         actions = action_PushString(actions, i->config_internallinkfunction); //function name
1645         actions = action_CallFunction(actions);
1646         actions = action_End(actions);
1647     }
1648
1649     char name[80];
1650     sprintf(name, "page%d", page);
1651
1652     drawlink(dev, actions, 0, points, 0, name);
1653     
1654     swf_ActionFree(actions);
1655 }
1656
1657 /* Named Links (a.k.a. Acrobatmenu) are used to implement various gadgets
1658    of the viewer objects, like subtitles, index elements etc.
1659 */
1660 void swfoutput_namedlink(gfxdevice_t*dev, char*name, gfxline_t*points)
1661 {
1662     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1663     ActionTAG *actions1,*actions2;
1664     char*tmp = strdup(name);
1665     char mouseover = 1;
1666
1667     if(i->shapeid>=0)
1668         endshape(dev);
1669     if(i->textid>=0)
1670         endtext(dev);
1671
1672     if(!strncmp(tmp, "call:", 5))
1673     {
1674         char*x = strchr(&tmp[5], ':');
1675         if(!x) {
1676             actions1 = action_PushInt(0, 0); //number of parameters (0)
1677             actions1 = action_PushString(actions1, &tmp[5]); //function name
1678             actions1 = action_CallFunction(actions1);
1679             actions1 = action_End(actions1);
1680         } else {
1681             *x = 0;
1682             actions1 = action_PushString(0, x+1); //parameter
1683             actions1 = action_PushInt(actions1, 1); //number of parameters (1)
1684             actions1 = action_PushString(actions1, &tmp[5]); //function name
1685             actions1 = action_CallFunction(actions1);
1686             actions1 = action_End(actions1);
1687         }
1688         actions2 = action_End(0);
1689         mouseover = 0;
1690     }
1691     else
1692     {
1693         actions1 = action_PushString(0, "/:subtitle");
1694         actions1 = action_PushString(actions1, name);
1695         actions1 = action_SetVariable(actions1);
1696         actions1 = action_End(actions1);
1697
1698         actions2 = action_PushString(0, "/:subtitle");
1699         actions2 = action_PushString(actions2, "");
1700         actions2 = action_SetVariable(actions2);
1701         actions2 = action_End(actions2);
1702     }
1703
1704     drawlink(dev, actions1, actions2, points, mouseover, name);
1705
1706     swf_ActionFree(actions1);
1707     swf_ActionFree(actions2);
1708     free(tmp);
1709 }
1710
1711 static void drawgfxline(gfxdevice_t*dev, gfxline_t*line, int fill)
1712 {
1713     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1714     gfxcoord_t lastx=0,lasty=0,px=0,py=0;
1715     char lastwasmoveto;
1716     int lines= 0, splines=0;
1717
1718     i->fill = fill;
1719
1720     while(1) {
1721         if(!line)
1722             break;
1723         /* check whether the next segment is zero */
1724         if(line->type == gfx_moveTo) {
1725             moveto(dev, i->tag, line->x, line->y);
1726             px = lastx = line->x;
1727             py = lasty = line->y;
1728             lastwasmoveto = 1;
1729         } if(line->type == gfx_lineTo) {
1730             lineto(dev, i->tag, line->x, line->y);
1731             px = line->x;
1732             py = line->y;
1733             lastwasmoveto = 0;
1734             lines++;
1735         } else if(line->type == gfx_splineTo) {
1736             plotxy_t s,p;
1737             s.x = line->sx;p.x = line->x;
1738             s.y = line->sy;p.y = line->y;
1739             splineto(dev, i->tag, s, p);
1740             px = line->x;
1741             py = line->y;
1742             lastwasmoveto = 0;
1743             splines++;
1744         }
1745         line = line->next;
1746     }
1747     msg("<trace> drawgfxline, %d lines, %d splines", lines, splines);
1748 }
1749
1750
1751 static void drawlink(gfxdevice_t*dev, ActionTAG*actions1, ActionTAG*actions2, gfxline_t*points, char mouseover, const char*url)
1752 {
1753     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1754     RGBA rgb;
1755     SRECT r;
1756     int lsid=0;
1757     int fsid;
1758     int myshapeid;
1759     int myshapeid2;
1760     double posx = 0;
1761     double posy = 0;
1762     int buttonid = getNewID(dev);
1763     gfxbbox_t bbox = gfxline_getbbox(points);
1764     
1765     i->hasbuttons = 1;
1766
1767     /* shape */
1768     myshapeid = getNewID(dev);
1769     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
1770     swf_ShapeNew(&i->shape);
1771     rgb.r = rgb.b = rgb.a = rgb.g = 0; 
1772     fsid = swf_ShapeAddSolidFillStyle(i->shape,&rgb);
1773     swf_SetU16(i->tag, myshapeid);
1774     r.xmin = (int)(bbox.xmin*20);
1775     r.ymin = (int)(bbox.ymin*20);
1776     r.xmax = (int)(bbox.xmax*20);
1777     r.ymax = (int)(bbox.ymax*20);
1778     r = swf_ClipRect(i->pagebbox, r);
1779     swf_SetRect(i->tag,&r);
1780     swf_SetShapeStyles(i->tag,i->shape);
1781     swf_ShapeCountBits(i->shape,NULL,NULL);
1782     swf_SetShapeBits(i->tag,i->shape);
1783     swf_ShapeSetAll(i->tag,i->shape,/*x*/0,/*y*/0,0,fsid,0);
1784     i->swflastx = i->swflasty = 0;
1785     drawgfxline(dev, points, 1);
1786     swf_ShapeSetEnd(i->tag);
1787     swf_ShapeFree(i->shape);
1788
1789     /* shape2 */
1790     myshapeid2 = getNewID(dev);
1791     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
1792     swf_ShapeNew(&i->shape);
1793     
1794     rgb = i->config_linkcolor;
1795
1796     fsid = swf_ShapeAddSolidFillStyle(i->shape,&rgb);
1797     swf_SetU16(i->tag, myshapeid2);
1798     r.xmin = (int)(bbox.xmin*20);
1799     r.ymin = (int)(bbox.ymin*20);
1800     r.xmax = (int)(bbox.xmax*20);
1801     r.ymax = (int)(bbox.ymax*20);
1802     r = swf_ClipRect(i->pagebbox, r);
1803     swf_SetRect(i->tag,&r);
1804     swf_SetShapeStyles(i->tag,i->shape);
1805     swf_ShapeCountBits(i->shape,NULL,NULL);
1806     swf_SetShapeBits(i->tag,i->shape);
1807     swf_ShapeSetAll(i->tag,i->shape,/*x*/0,/*y*/0,0,fsid,0);
1808     i->swflastx = i->swflasty = 0;
1809     drawgfxline(dev, points, 1);
1810     swf_ShapeSetEnd(i->tag);
1811     swf_ShapeFree(i->shape);
1812
1813     if(!mouseover)
1814     {
1815         i->tag = swf_InsertTag(i->tag,ST_DEFINEBUTTON);
1816         swf_SetU16(i->tag,buttonid); //id
1817         swf_ButtonSetFlags(i->tag, 0); //menu=no
1818         swf_ButtonSetRecord(i->tag,0x01,myshapeid,i->depth,0,0);
1819         swf_ButtonSetRecord(i->tag,0x02,myshapeid2,i->depth,0,0);
1820         swf_ButtonSetRecord(i->tag,0x04,myshapeid2,i->depth,0,0);
1821         swf_ButtonSetRecord(i->tag,0x08,myshapeid,i->depth,0,0);
1822         swf_SetU8(i->tag,0);
1823         swf_ActionSet(i->tag,actions1);
1824         swf_SetU8(i->tag,0);
1825     }
1826     else
1827     {
1828         i->tag = swf_InsertTag(i->tag,ST_DEFINEBUTTON2);
1829         swf_SetU16(i->tag,buttonid); //id
1830         swf_ButtonSetFlags(i->tag, 0); //menu=no
1831         swf_ButtonSetRecord(i->tag,0x01,myshapeid,i->depth,0,0);
1832         swf_ButtonSetRecord(i->tag,0x02,myshapeid2,i->depth,0,0);
1833         swf_ButtonSetRecord(i->tag,0x04,myshapeid2,i->depth,0,0);
1834         swf_ButtonSetRecord(i->tag,0x08,myshapeid,i->depth,0,0);
1835         swf_SetU8(i->tag,0); // end of button records
1836         swf_ButtonSetCondition(i->tag, BC_IDLE_OVERUP);
1837         swf_ActionSet(i->tag,actions1);
1838         if(actions2) {
1839             swf_ButtonSetCondition(i->tag, BC_OVERUP_IDLE);
1840             swf_ActionSet(i->tag,actions2);
1841             swf_SetU8(i->tag,0);
1842             swf_ButtonPostProcess(i->tag, 2);
1843         } else {
1844             swf_SetU8(i->tag,0);
1845             swf_ButtonPostProcess(i->tag, 1);
1846         }
1847     }
1848     char buf[80];
1849     const char* name = 0;
1850     if(i->config_linknameurl) {
1851         name = url;
1852     } else {
1853         name = buf;
1854         sprintf(buf, "button%d", buttonid);
1855     }
1856     
1857     msg("<trace> Placing link ID %d", buttonid);
1858     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
1859
1860     if(posx!=0 || posy!=0) {
1861         SPOINT p;
1862         p.x = (int)(posx*20);
1863         p.y = (int)(posy*20);
1864         p = swf_TurnPoint(p, &i->page_matrix);
1865         MATRIX m;
1866         m = i->page_matrix;
1867         m.tx = p.x;
1868         m.ty = p.y;
1869         swf_ObjectPlace(i->tag, buttonid, getNewDepth(dev),&m,0,name);
1870     } else {
1871         swf_ObjectPlace(i->tag, buttonid, getNewDepth(dev),&i->page_matrix,0,name);
1872     }
1873 }
1874
1875       
1876 ///////////
1877 /*
1878 for(t=0;t<picpos;t++)
1879       {
1880           if(pic_xids[t] == xid &&
1881              pic_yids[t] == yid) {
1882               width = pic_width[t];
1883               height = pic_height[t];
1884               found = t;break;
1885           }
1886       }
1887           pic_ids[picpos] = swfoutput_drawimagelosslessN(&output, pic, pal, width, height, x1,y1,x2,y2,x3,y3,x4,y4, numpalette);
1888           pic_xids[picpos] = xid;
1889           pic_yids[picpos] = yid;
1890           pic_width[picpos] = width;
1891           pic_height[picpos] = height;
1892           if(picpos<1024)
1893               picpos++;
1894             pic[width*y+x] = buf[0];
1895             xid+=x*buf[0]+1;
1896             yid+=y*buf[0]*3+1;
1897       
1898             xid += pal[1].r*3 + pal[1].g*11 + pal[1].b*17;
1899       yid += pal[1].r*7 + pal[1].g*5 + pal[1].b*23;
1900       
1901       int xid = 0;
1902       int yid = 0;
1903           xid += x*r+x*b*3+x*g*7+x*a*11;
1904           yid += y*r*3+y*b*17+y*g*19+y*a*11;
1905       int t,found = -1;
1906       for(t=0;t<picpos;t++)
1907       {
1908           if(pic_xids[t] == xid &&
1909              pic_yids[t] == yid) {
1910               found = t;break;
1911           }
1912       }
1913       if(found<0) {
1914 */
1915 ///////////
1916
1917
1918 int swf_setparameter(gfxdevice_t*dev, const char*name, const char*value)
1919 {
1920     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1921
1922     msg("<trace> swfdevice: %s=%s", name, value);
1923     if(!strcmp(name, "jpegsubpixels")) {
1924         i->config_jpegsubpixels = atof(value);
1925     } else if(!strcmp(name, "ppmsubpixels")) {
1926         i->config_ppmsubpixels = atof(value);
1927     } else if(!strcmp(name, "subpixels")) {
1928         i->config_ppmsubpixels = i->config_jpegsubpixels = atof(value);
1929     } else if(!strcmp(name, "drawonlyshapes")) {
1930         i->config_drawonlyshapes = atoi(value);
1931     } else if(!strcmp(name, "ignoredraworder")) {
1932         i->config_ignoredraworder = atoi(value);
1933     } else if(!strcmp(name, "mark")) {
1934         if(!value || !value[0]) {
1935             if(i->mark) free(i->mark);
1936             i->mark = 0;
1937         } else {
1938             int t;
1939             i->mark = strdup("...");
1940             for(t=0;t<3;t++) if(value[t]) i->mark[t] = value[t];
1941         }
1942     } else if(!strcmp(name, "filloverlap")) {
1943         i->config_filloverlap = atoi(value);
1944     } else if(!strcmp(name, "linksopennewwindow")) {
1945         i->config_opennewwindow = atoi(value);
1946     } else if(!strcmp(name, "opennewwindow")) {
1947         i->config_opennewwindow = atoi(value);
1948     } else if(!strcmp(name, "storeallcharacters")) {
1949         i->config_storeallcharacters = atoi(value);
1950     } else if(!strcmp(name, "enablezlib")) {
1951         i->config_enablezlib = atoi(value);
1952     } else if(!strcmp(name, "bboxvars")) {
1953         i->config_bboxvars = atoi(value);
1954     } else if(!strcmp(name, "dots")) {
1955         i->config_dots = atoi(value);
1956     } else if(!strcmp(name, "frameresets")) {
1957         i->config_frameresets = atoi(value);
1958     } else if(!strcmp(name, "showclipshapes")) {
1959         i->config_showclipshapes = atoi(value);
1960     } else if(!strcmp(name, "reordertags")) {
1961         i->config_reordertags = atoi(value);
1962     } else if(!strcmp(name, "internallinkfunction")) {
1963         i->config_internallinkfunction = strdup(value);
1964     } else if(!strcmp(name, "externallinkfunction")) {
1965         i->config_externallinkfunction = strdup(value);
1966     } else if(!strcmp(name, "linkfunction")) { //sets both internallinkfunction and externallinkfunction
1967         i->config_internallinkfunction = strdup(value);
1968         i->config_externallinkfunction = strdup(value);
1969     } else if(!strcmp(name, "disable_polygon_conversion")) {
1970         i->config_disable_polygon_conversion = atoi(value);
1971     } else if(!strcmp(name, "normalize_polygon_positions")) {
1972         i->config_normalize_polygon_positions = atoi(value);
1973     } else if(!strcmp(name, "wxwindowparams")) {
1974         i->config_watermark = atoi(value);
1975     } else if(!strcmp(name, "insertstop")) {
1976         i->config_insertstoptag = atoi(value);
1977     } else if(!strcmp(name, "protect")) {
1978         i->config_protect = atoi(value);
1979         if(i->config_protect && i->tag) {
1980             i->tag = swf_InsertTag(i->tag, ST_PROTECT);
1981         }
1982     } else if(!strcmp(name, "flashversion")) {
1983         i->config_flashversion = atoi(value);
1984         if(i->swf) {
1985             i->swf->fileVersion = i->config_flashversion;
1986         }
1987     } else if(!strcmp(name, "framerate")) {
1988         i->config_framerate = atof(value);
1989         if(i->swf) {
1990             i->swf->frameRate = i->config_framerate*0x100;
1991         }
1992     } else if(!strcmp(name, "minlinewidth")) {
1993         i->config_minlinewidth = atof(value);
1994     } else if(!strcmp(name, "caplinewidth")) {
1995         i->config_caplinewidth = atof(value);
1996     } else if(!strcmp(name, "linktarget")) {
1997         i->config_linktarget = strdup(value);
1998     } else if(!strcmp(name, "dumpfonts")) {
1999         i->config_dumpfonts = atoi(value);
2000     } else if(!strcmp(name, "animate")) {
2001         i->config_animate = atoi(value);
2002     } else if(!strcmp(name, "disablelinks")) {
2003         i->config_disablelinks = atoi(value);
2004     } else if(!strcmp(name, "simpleviewer")) {
2005         i->config_simpleviewer = atoi(value);
2006     } else if(!strcmp(name, "next_bitmap_is_jpeg")) {
2007         i->jpeg = 1;
2008     } else if(!strcmp(name, "jpegquality")) {
2009         int val = atoi(value);
2010         if(val<0) val=0;
2011         if(val>101) val=101;
2012         i->config_jpegquality = val;
2013     } else if(!strcmp(name, "splinequality")) {
2014         int v = atoi(value);
2015         v = 500-(v*5); // 100% = 0.25 pixel, 0% = 25 pixel
2016         if(v<1) v = 1;
2017         i->config_splinemaxerror = v;
2018     } else if(!strcmp(name, "fontquality")) {
2019         int v = atoi(value);
2020         v = 500-(v*5); // 100% = 0.25 pixel, 0% = 25 pixel
2021         if(v<1) v = 1;
2022         i->config_fontsplinemaxerror = v;
2023     } else if(!strcmp(name, "linkcolor")) {
2024         if(strlen(value)!=8) {
2025             fprintf(stderr, "Unknown format for option 'linkcolor'. (%s <-> RRGGBBAA)\n", value);
2026             return 1;
2027         }
2028 #       define NIBBLE(s) (((s)>='0' && (s)<='9')?((s)-'0'):((s)&0x0f)+9)
2029         i->config_linkcolor.r = NIBBLE(value[0])<<4 | NIBBLE(value[1]);
2030         i->config_linkcolor.g = NIBBLE(value[2])<<4 | NIBBLE(value[3]);
2031         i->config_linkcolor.b = NIBBLE(value[4])<<4 | NIBBLE(value[5]);
2032         i->config_linkcolor.a = NIBBLE(value[6])<<4 | NIBBLE(value[7]);
2033     } else if(!strcmp(name, "help")) {
2034         printf("\nSWF layer options:\n");
2035         printf("jpegsubpixels=<pixels>      resolution adjustment for jpeg images (same as jpegdpi, but in pixels)\n");
2036         printf("ppmsubpixels=<pixels        resolution adjustment for  lossless images (same as ppmdpi, but in pixels)\n");
2037         printf("subpixels=<pixels>          shortcut for setting both jpegsubpixels and ppmsubpixels\n");
2038         printf("drawonlyshapes              convert everything to shapes (currently broken)\n");
2039         printf("ignoredraworder             allow to perform a few optimizations for creating smaller SWFs\n");
2040         printf("linksopennewwindow          make links open a new browser window\n");
2041         printf("linktarget                  target window name of new links\n");
2042         printf("linkcolor=<color)           color of links (format: RRGGBBAA)\n");
2043         printf("linknameurl                 Link buttons will be named like the URL they refer to (handy for iterating through links with actionscript)\n");
2044         printf("storeallcharacters          don't reduce the fonts to used characters in the output file\n");
2045         printf("enablezlib                  switch on zlib compression (also done if flashversion>=7)\n");
2046         printf("bboxvars                    store the bounding box of the SWF file in actionscript variables\n");
2047         printf("dots                        Take care to handle dots correctly\n");
2048         printf("reordertags=0/1             (default: 1) perform some tag optimizations\n");
2049         printf("internallinkfunction=<name> when the user clicks a internal link (to a different page) in the converted file, this actionscript function is called\n");
2050         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");
2051         printf("disable_polygon_conversion  never convert strokes to polygons (will remove capstyles and joint styles)\n");
2052         printf("caplinewidth=<width>        the minimum thichness a line needs to have so that capstyles become visible (and are converted)\n");
2053         printf("insertstop                  put an ActionScript \"STOP\" tag in every frame\n");
2054         printf("protect                     add a \"protect\" tag to the file, to prevent loading in the Flash editor\n");
2055         printf("flashversion=<version>      the SWF fileversion (6)\n");
2056         printf("framerate=<fps>             SWF framerate\n");
2057         printf("minlinewidth=<width>        convert horizontal/vertical boxes smaller than this width to lines (0.05) \n");
2058         printf("simpleviewer                Add next/previous buttons to the SWF\n");
2059         printf("animate                     insert a showframe tag after each placeobject (animate draw order of PDF files)\n");
2060         printf("jpegquality=<quality>       set compression quality of jpeg images\n");
2061         printf("splinequality=<value>       Set the quality of spline convertion to value (0-100, default: 100).\n");
2062         printf("disablelinks                Disable links.\n");
2063     } else {
2064         return 0;
2065     }
2066     return 1;
2067 }
2068
2069 // --------------------------------------------------------------------
2070
2071 static CXFORM gfxcxform_to_cxform(gfxcxform_t* c)
2072 {
2073     CXFORM cx;
2074     swf_GetCXForm(0, &cx, 1);
2075     if(!c)
2076         return cx;
2077     if(c->rg!=0 || c->rb!=0 || c->ra!=0 ||
2078        c->gr!=0 || c->gb!=0 || c->ga!=0 ||
2079        c->br!=0 || c->bg!=0 || c->ba!=0 ||
2080        c->ar!=0 || c->ag!=0 || c->ab!=0)
2081         msg("<warning> CXForm not SWF-compatible");
2082
2083     cx.a0 = (S16)(c->aa*256);
2084     cx.r0 = (S16)(c->rr*256);
2085     cx.g0 = (S16)(c->gg*256);
2086     cx.b0 = (S16)(c->bb*256);
2087     cx.a1 = c->ta;
2088     cx.r1 = c->tr;
2089     cx.g1 = c->tg;
2090     cx.b1 = c->tb;
2091     return cx;
2092 }
2093
2094 /* TODO */
2095 static int imageInCache(gfxdevice_t*dev, void*data, int width, int height)
2096 {
2097     return -1;
2098 }
2099 static void addImageToCache(gfxdevice_t*dev, void*data, int width, int height)
2100 {
2101 }
2102     
2103 static int add_image(swfoutput_internal*i, gfximage_t*img, int targetwidth, int targetheight, int* newwidth, int* newheight)
2104 {
2105     gfxdevice_t*dev = i->dev;
2106     RGBA*newpic = 0;
2107     RGBA*mem = (RGBA*)img->data;
2108     
2109     int sizex = img->width;
2110     int sizey = img->height;
2111     int is_jpeg = i->jpeg;
2112     i->jpeg = 0;
2113
2114     int newsizex=sizex, newsizey=sizey;
2115
2116     /// {
2117     if(is_jpeg && i->config_jpegsubpixels) {
2118         newsizex = (int)(targetwidth*i->config_jpegsubpixels + 0.5);
2119         newsizey = (int)(targetheight*i->config_jpegsubpixels + 0.5);
2120     } else if(!is_jpeg && i->config_ppmsubpixels) {
2121         newsizex = (int)(targetwidth*i->config_ppmsubpixels + 0.5);
2122         newsizey = (int)(targetheight*i->config_ppmsubpixels + 0.5);
2123     }
2124     /// }
2125
2126     if(sizex<=0 || sizey<=0)
2127         return -1;
2128     if(newsizex<=0)
2129         newsizex = 1;
2130     if(newsizey<=0)
2131         newsizey = 1;
2132
2133     /* TODO: cache images */
2134     
2135     if(newsizex<sizex || newsizey<sizey) {
2136         msg("<verbose> Scaling %dx%d image to %dx%d", sizex, sizey, newsizex, newsizey);
2137         newpic = swf_ImageScale(mem, sizex, sizey, newsizex, newsizey);
2138         *newwidth = sizex = newsizex;
2139         *newheight  = sizey = newsizey;
2140         mem = newpic;
2141     } else {
2142         *newwidth = newsizex = sizex;
2143         *newheight = newsizey  = sizey;
2144     }
2145
2146     int num_colors = swf_ImageGetNumberOfPaletteEntries(mem,sizex,sizey,0);
2147     int has_alpha = swf_ImageHasAlpha(mem,sizex,sizey);
2148     
2149     msg("<verbose> Drawing %dx%d %s%simage (id %d) at size %dx%d (%dx%d), %s%d colors",
2150             sizex, sizey, 
2151             has_alpha?(has_alpha==2?"semi-transparent ":"transparent "):"", 
2152             is_jpeg?"jpeg-":"", i->currentswfid+1,
2153             newsizex, newsizey,
2154             targetwidth, targetheight,
2155             /*newsizex, newsizey,*/
2156             num_colors>256?">":"", num_colors>256?256:num_colors);
2157
2158     /*RGBA* pal = (RGBA*)rfx_alloc(sizeof(RGBA)*num_colors);
2159     swf_ImageGetNumberOfPaletteEntries(mem,sizex,sizey,pal);
2160     int t;
2161     for(t=0;t<num_colors;t++) {
2162         printf("%02x%02x%02x%02x ",
2163                 pal[t].r, pal[t].g, pal[t].b, pal[t].a);
2164         if((t&7)==7)
2165             printf("\n");
2166     }
2167     printf("\n");*/
2168
2169     int bitid = -1;
2170     int cacheid = imageInCache(dev, mem, sizex, sizey);
2171
2172     if(cacheid<=0) {
2173         bitid = getNewID(dev);
2174
2175         i->tag = swf_AddImage(i->tag, bitid, mem, sizex, sizey, i->config_jpegquality);
2176         addImageToCache(dev, mem, sizex, sizey);
2177     } else {
2178         bitid = cacheid;
2179     }
2180
2181     if(newpic)
2182         free(newpic);
2183     return bitid;
2184 }
2185
2186 static SRECT gfxline_getSWFbbox(gfxline_t*line)
2187 {
2188     gfxbbox_t bbox = gfxline_getbbox(line);
2189     SRECT r;
2190     r.xmin = (int)(bbox.xmin*20);
2191     r.ymin = (int)(bbox.ymin*20);
2192     r.xmax = (int)(bbox.xmax*20);
2193     r.ymax = (int)(bbox.ymax*20);
2194     return r;
2195 }
2196
2197 int line_is_empty(gfxline_t*line)
2198 {
2199     while(line) {
2200         if(line->type != gfx_moveTo)
2201             return 0;
2202         line = line->next;
2203     }
2204     return 1;
2205 }
2206
2207 static void swf_fillbitmap(gfxdevice_t*dev, gfxline_t*line, gfximage_t*img, gfxmatrix_t*matrix, gfxcxform_t*cxform)
2208 {
2209     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2210     
2211     if(line_is_empty(line))
2212         return;
2213
2214     endshape(dev);
2215     endtext(dev);
2216
2217     int targetx = (int)(sqrt(matrix->m00*matrix->m00 + matrix->m01*matrix->m01)*img->width);
2218     int targety = (int)(sqrt(matrix->m10*matrix->m10 + matrix->m11*matrix->m11)*img->height);
2219
2220     int newwidth=0,newheight=0;
2221     int bitid = add_image(i, img, targetx, targety, &newwidth, &newheight);
2222     if(bitid<0)
2223         return;
2224     double fx = (double)img->width / (double)newwidth;
2225     double fy = (double)img->height / (double)newheight;
2226
2227     MATRIX m;
2228     m.sx = (int)(65536*20*matrix->m00*fx); m.r1 = (int)(65536*20*matrix->m10*fy);
2229     m.r0 = (int)(65536*20*matrix->m01*fx); m.sy = (int)(65536*20*matrix->m11*fy);
2230     m.tx = (int)(matrix->tx*20);
2231     m.ty = (int)(matrix->ty*20);
2232   
2233     /* shape */
2234     int myshapeid = getNewID(dev);
2235     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE);
2236     SHAPE*shape;
2237     swf_ShapeNew(&shape);
2238     int fsid = swf_ShapeAddBitmapFillStyle(shape,&m,bitid,1);
2239     swf_SetU16(i->tag, myshapeid);
2240     SRECT r = gfxline_getSWFbbox(line);
2241     r = swf_ClipRect(i->pagebbox, r);
2242     swf_SetRect(i->tag,&r);
2243     swf_SetShapeStyles(i->tag,shape);
2244     swf_ShapeCountBits(shape,NULL,NULL);
2245     swf_SetShapeBits(i->tag,shape);
2246     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2247     i->swflastx = i->swflasty = UNDEFINED_COORD;
2248     drawgfxline(dev, line, 1);
2249     swf_ShapeSetEnd(i->tag);
2250     swf_ShapeFree(shape);
2251
2252     msg("<trace> Placing image, shape ID %d, bitmap ID %d", myshapeid, bitid);
2253     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2254     CXFORM cxform2 = gfxcxform_to_cxform(cxform);
2255     swf_ObjectPlace(i->tag,myshapeid,getNewDepth(dev),&i->page_matrix,&cxform2,NULL);
2256 }
2257
2258 static RGBA col_black = {255,0,0,0};
2259
2260 static void drawoutline(gfxdevice_t*dev, gfxline_t*line)
2261 {
2262     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2263
2264     int myshapeid = getNewID(dev);
2265     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
2266
2267     SHAPE*shape;
2268     swf_ShapeNew(&shape);
2269     int lsid = swf_ShapeAddLineStyle(shape,1,&col_black);
2270
2271     swf_SetU16(i->tag,myshapeid);
2272     SRECT r = gfxline_getSWFbbox(line);
2273     r = swf_ClipRect(i->pagebbox, r);
2274     swf_SetRect(i->tag,&r);
2275     swf_SetShapeStyles(i->tag,shape);
2276     swf_ShapeCountBits(shape,NULL,NULL);
2277     swf_SetShapeBits(i->tag,shape);
2278     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,lsid,0,0);
2279     drawgfxline(dev, line, 1);
2280     swf_ShapeSetEnd(i->tag);
2281     swf_ShapeFree(shape);
2282         
2283     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2284     swf_ObjectPlace(i->tag, myshapeid, getNewDepth(dev), 0,0,0);
2285 }
2286
2287 static void swf_startclip(gfxdevice_t*dev, gfxline_t*line)
2288 {
2289     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2290
2291     endtext(dev);
2292     endshape(dev);
2293
2294     if(i->clippos >= 127)
2295     {
2296         msg("<warning> Too many clip levels.");
2297         i->clippos --;
2298     } 
2299
2300     if(i->config_showclipshapes)
2301         drawoutline(dev, line);
2302
2303     int myshapeid = getNewID(dev);
2304     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
2305     RGBA col;
2306     memset(&col, 0, sizeof(RGBA));
2307     col.a = 255;
2308     SHAPE*shape;
2309     swf_ShapeNew(&shape);
2310     int fsid = swf_ShapeAddSolidFillStyle(shape,&col);
2311     if(i->mark) {
2312         RGBA markcol = {0,i->mark[0],i->mark[1],i->mark[2]};
2313         swf_ShapeAddSolidFillStyle(shape,&markcol);
2314     }
2315     swf_SetU16(i->tag,myshapeid);
2316     SRECT r = gfxline_getSWFbbox(line);
2317     r = swf_ClipRect(i->pagebbox, r);
2318     swf_SetRect(i->tag,&r);
2319     swf_SetShapeStyles(i->tag,shape);
2320     swf_ShapeCountBits(shape,NULL,NULL);
2321     swf_SetShapeBits(i->tag,shape);
2322     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2323     i->swflastx = i->swflasty = UNDEFINED_COORD;
2324     i->shapeisempty = 1;
2325     drawgfxline(dev, line, 1);
2326     if(i->shapeisempty) {
2327         /* an empty clip shape is equivalent to a shape with no area */
2328         int x = line?line->x:0;
2329         int y = line?line->y:0;
2330         moveto(dev, i->tag, x,y);
2331         lineto(dev, i->tag, x,y);
2332         lineto(dev, i->tag, x,y);
2333     }
2334     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)) {
2335         if(i->config_watermark) {
2336             gfxbbox_t r; r.xmin = r.ymin = 0;r.xmax = i->max_x;r.ymax = i->max_y;
2337             draw_watermark(dev, r, 1);
2338         }
2339     }
2340     swf_ShapeSetEnd(i->tag);
2341     swf_ShapeFree(shape);
2342
2343     /* TODO: remember the bbox, and check all shapes against it */
2344     
2345     msg("<trace> Placing clip ID %d", myshapeid);
2346     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2347     i->cliptags[i->clippos] = i->tag;
2348     i->clipshapes[i->clippos] = myshapeid;
2349     i->clipdepths[i->clippos] = getNewDepth(dev);
2350     i->clippos++;
2351 }
2352
2353 static void swf_endclip(gfxdevice_t*dev)
2354 {
2355     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2356     if(i->textid>=0)
2357         endtext(dev);
2358     if(i->shapeid>=0)
2359         endshape(dev);
2360
2361     if(!i->clippos) {
2362         msg("<error> Invalid end of clipping region");
2363         return;
2364     }
2365     i->clippos--;
2366     /*swf_ObjectPlaceClip(i->cliptags[i->clippos],i->clipshapes[i->clippos],i->clipdepths[i->clippos],&i->page_matrix,NULL,NULL,
2367             / * clip to depth: * / i->depth <= i->clipdepths[i->clippos]? i->depth : i->depth - 1);
2368     i->depth ++;*/
2369     swf_ObjectPlaceClip(i->cliptags[i->clippos],i->clipshapes[i->clippos],i->clipdepths[i->clippos],&i->page_matrix,NULL,NULL,i->depth);
2370 }
2371 static int gfxline_type(gfxline_t*line)
2372 {
2373     int tmplines=0;
2374     int tmpsplines=0;
2375     int lines=0;
2376     int splines=0;
2377     int haszerosegments=0;
2378     int length=0;
2379     while(line) {
2380         if(line->type == gfx_moveTo) {
2381             tmplines=0;
2382             tmpsplines=0;
2383         } else if(line->type == gfx_lineTo) {
2384             tmplines++;
2385             if(tmplines>lines)
2386                 lines=tmplines;
2387         } else if(line->type == gfx_splineTo) {
2388             tmpsplines++;
2389             if(tmpsplines>lines)
2390                 splines=tmpsplines;
2391         }
2392         length++;
2393         line = line->next;
2394     }
2395     if(length>400)
2396         return 5;
2397     if(lines==0 && splines==0) return 0;
2398     else if(lines==1 && splines==0) return 1;
2399     else if(lines==0 && splines==1) return 2;
2400     else if(splines==0) return 3;
2401     else return 4;
2402 }
2403
2404 static int gfxline_has_dots(gfxline_t*line)
2405 {
2406     int tmplines=0;
2407     double x=0,y=0;
2408     double dist = 0;
2409     int isline = 0;
2410     int short_gap = 0;
2411     while(line) {
2412         if(line->type == gfx_moveTo) {
2413             /* test the length of the preceding line, and assume it is a dot if
2414                it's length is less than 1.0. But *only* if there's a noticable 
2415                gap between the previous line and the next moveTo. (I've come
2416                across a PDF where thousands of "dots" were stringed together,
2417                forming a line) */
2418             int last_short_gap = short_gap;
2419             if((fabs(line->x - x) + fabs(line->y - y)) < 1.0) {
2420                 short_gap = 1;
2421             } else {
2422                 short_gap = 0;
2423             }
2424             if(isline && dist < 1 && !short_gap && !last_short_gap) {
2425                 return 1;
2426             }
2427             dist = 0;
2428             isline = 0;
2429         } else if(line->type == gfx_lineTo) {
2430             dist += fabs(line->x - x) + fabs(line->y - y);
2431             isline = 1;
2432         } else if(line->type == gfx_splineTo) {
2433             dist += fabs(line->sx - x) + fabs(line->sy - y) + 
2434                     fabs(line->x - line->sx) + fabs(line->y - line->sy);
2435             isline = 1;
2436         }
2437         x = line->x;
2438         y = line->y;
2439         line = line->next;
2440     }
2441     if(isline && dist < 1 && !short_gap) {
2442         return 1;
2443     }
2444     return 0;
2445 }
2446
2447 static int gfxline_fix_short_edges(gfxline_t*line)
2448 {
2449     double x,y;
2450     while(line) {
2451         if(line->type == gfx_lineTo) {
2452             if(fabs(line->x - x) + fabs(line->y - y) < 0.01) {
2453                 line->x += 0.01;
2454             }
2455         } else if(line->type == gfx_splineTo) {
2456             if(fabs(line->sx - x) + fabs(line->sy - y) + 
2457                fabs(line->x - line->sx) + fabs(line->y - line->sy) < 0.01) {
2458                 line->x += 0.01;
2459             }
2460         }
2461         x = line->x;
2462         y = line->y;
2463         line = line->next;
2464     }
2465     return 0;
2466 }
2467
2468 static char is_inside_page(gfxdevice_t*dev, gfxcoord_t x, gfxcoord_t y)
2469 {
2470     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2471     if(x<i->min_x || x>i->max_x) return 0;
2472     if(y<i->min_y || y>i->max_y) return 0;
2473     return 1;
2474 }
2475
2476 gfxline_t* gfxline_move(gfxline_t*line, double x, double y)
2477 {
2478     gfxline_t*l = line = gfxline_clone(line);
2479
2480     while(l) {
2481         l->x += x;
2482         l->y += y;
2483         l->sx += x;
2484         l->sy += y;
2485         l = l->next;
2486     }
2487     return line;
2488 }
2489
2490 //#define NORMALIZE_POLYGON_POSITIONS
2491
2492 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)
2493 {
2494     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2495     if(line_is_empty(line))
2496         return;
2497     int type = gfxline_type(line);
2498     int has_dots = gfxline_has_dots(line);
2499     gfxbbox_t r = gfxline_getbbox(line);
2500     int is_outside_page = !is_inside_page(dev, r.xmin, r.ymin) || !is_inside_page(dev, r.xmax, r.ymax);
2501
2502     /* TODO: * split line into segments, and perform this check for all segments */
2503
2504     if(i->config_disable_polygon_conversion || /*type>=5 ||*/
2505        (!has_dots &&
2506         (width <= i->config_caplinewidth 
2507         || (cap_style == gfx_capRound && joint_style == gfx_joinRound)
2508         || (cap_style == gfx_capRound && type<=2)))) 
2509     {
2510         // ...
2511     } else {
2512         /* convert line to polygon */
2513         msg("<trace> draw as polygon, type=%d dots=%d", type, has_dots);
2514         if(has_dots)
2515             gfxline_fix_short_edges(line);
2516         /* we need to convert the line into a polygon */
2517         gfxpoly_t* poly = gfxpoly_strokeToPoly(line, width, cap_style, joint_style, miterLimit);
2518         gfxline_t*gfxline = gfxpoly_to_gfxline(poly);
2519         dev->fill(dev, gfxline, color);
2520         gfxline_free(gfxline);
2521         gfxpoly_free(poly);
2522         return;
2523     }
2524
2525     msg("<trace> draw as stroke, type=%d dots=%d", type, has_dots);
2526     endtext(dev);
2527
2528     if(i->config_normalize_polygon_positions) {
2529         endshape(dev);
2530         double startx = 0, starty = 0;
2531         if(line && line->type == gfx_moveTo) {
2532             startx = line->x;
2533             starty = line->y;
2534         }
2535         line = gfxline_move(line, -startx, -starty);
2536         i->shapeposx = (int)(startx*20);
2537         i->shapeposy = (int)(starty*20);
2538     }
2539
2540     swfoutput_setstrokecolor(dev, color->r, color->g, color->b, color->a);
2541     swfoutput_setlinewidth(dev, width);
2542     startshape(dev);
2543     stopFill(dev);
2544     drawgfxline(dev, line, 0);
2545
2546     if(i->config_normalize_polygon_positions) {
2547         free(line); //account for _move
2548     }
2549
2550 }
2551
2552 static void swf_fill(gfxdevice_t*dev, gfxline_t*line, gfxcolor_t*color)
2553 {
2554     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2555     if(line_is_empty(line))
2556         return;
2557     if(!color->a)
2558         return;
2559     gfxbbox_t r = gfxline_getbbox(line);
2560     int is_outside_page = !is_inside_page(dev, r.xmin, r.ymin) || !is_inside_page(dev, r.xmax, r.ymax);
2561
2562     //if(i->chardatapos && i->chardata[i->chardatapos-1].color.a) {
2563     endtext(dev);
2564
2565     if(!i->config_ignoredraworder)
2566         endshape(dev);
2567
2568     if(i->config_normalize_polygon_positions) {
2569         endshape(dev);
2570         double startx = 0, starty = 0;
2571         if(line && line->type == gfx_moveTo) {
2572             startx = line->x;
2573             starty = line->y;
2574         }
2575         line = gfxline_move(line, -startx, -starty);
2576         i->shapeposx = (int)(startx*20);
2577         i->shapeposy = (int)(starty*20);
2578     }
2579
2580     swfoutput_setfillcolor(dev, color->r, color->g, color->b, color->a);
2581     startshape(dev);
2582     startFill(dev);
2583     drawgfxline(dev, line, 1);
2584     
2585     if(i->currentswfid==2 && r.xmin==0 && r.ymin==0 && r.xmax==i->max_x && r.ymax==i->max_y) {
2586         if(i->config_watermark) {
2587             draw_watermark(dev, r, 1);
2588         }
2589     }
2590
2591     msg("<trace> end of swf_fill (shapeid=%d)", i->shapeid);
2592
2593     if(i->config_normalize_polygon_positions) {
2594         free(line); //account for _move
2595     }
2596 }
2597
2598 static GRADIENT* gfxgradient_to_GRADIENT(gfxgradient_t*gradient)
2599 {
2600     int num = 0;
2601     gfxgradient_t*g = gradient;
2602     while(g) {
2603         num++;
2604         g = g->next;
2605     }
2606     GRADIENT* swfgradient = malloc(sizeof(GRADIENT));
2607     swfgradient->num = num;
2608     swfgradient->rgba = malloc(sizeof(swfgradient->rgba[0])*num);
2609     swfgradient->ratios = malloc(sizeof(swfgradient->ratios[0])*num);
2610
2611     g = gradient;
2612     num = 0;
2613     while(g) {
2614         swfgradient->ratios[num] = g->pos*255;
2615         swfgradient->rgba[num] = *(RGBA*)&g->color;
2616         num++;
2617         g = g->next;
2618     }
2619     return swfgradient;
2620 }
2621
2622 static void swf_fillgradient(gfxdevice_t*dev, gfxline_t*line, gfxgradient_t*gradient, gfxgradienttype_t type, gfxmatrix_t*matrix)
2623 {
2624     if(line_is_empty(line))
2625         return;
2626     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2627     
2628     if(line_is_empty(line))
2629         return;
2630
2631     GRADIENT* swfgradient = gfxgradient_to_GRADIENT(gradient);
2632     if(!swfgradient)
2633         return;
2634   
2635     endshape(dev);
2636     endtext(dev);
2637
2638     double f = type==gfxgradient_radial?4:4;
2639     MATRIX m;
2640     m.sx = (int)(matrix->m00*20*f); m.r1 = (int)(matrix->m10*20*f);
2641     m.r0 = (int)(matrix->m01*20*f); m.sy = (int)(matrix->m11*20*f);
2642     m.tx = (int)(matrix->tx*20);
2643     m.ty = (int)(matrix->ty*20);
2644
2645     /* shape */
2646     int myshapeid = getNewID(dev);
2647     i->tag = swf_InsertTag(i->tag, ST_DEFINESHAPE2);
2648     SHAPE*shape;
2649     swf_ShapeNew(&shape);
2650     int fsid = swf_ShapeAddGradientFillStyle(shape,&m,swfgradient,type==gfxgradient_radial);
2651     swf_SetU16(i->tag, myshapeid);
2652     SRECT r = gfxline_getSWFbbox(line);
2653     r = swf_ClipRect(i->pagebbox, r);
2654     swf_SetRect(i->tag,&r);
2655     swf_SetShapeStyles(i->tag,shape);
2656     swf_ShapeCountBits(shape,NULL,NULL);
2657     swf_SetShapeBits(i->tag,shape);
2658     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2659     i->swflastx = i->swflasty = UNDEFINED_COORD;
2660     drawgfxline(dev, line, 1);
2661     swf_ShapeSetEnd(i->tag);
2662     swf_ShapeFree(shape);
2663
2664     int depth = getNewDepth(dev);
2665     msg("<trace> Placing gradient, shape ID %d, depth %d", myshapeid, depth);
2666     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2667     swf_ObjectPlace(i->tag,myshapeid,depth,&i->page_matrix,NULL,NULL);
2668
2669     swf_FreeGradient(swfgradient);free(swfgradient);
2670 }
2671
2672 static SWFFONT* gfxfont_to_swffont(gfxfont_t*font, const char* id)
2673 {
2674     SWFFONT*swffont = (SWFFONT*)rfx_calloc(sizeof(SWFFONT));
2675     int t;
2676     SRECT bounds = {0,0,0,0};
2677     swffont->id = -1;
2678     swffont->version = 2;
2679     swffont->name = (U8*)strdup(id);
2680     swffont->layout = (SWFLAYOUT*)rfx_calloc(sizeof(SWFLAYOUT));
2681     swffont->layout->ascent = 0;
2682     swffont->layout->descent = 0;
2683     swffont->layout->leading = 0;
2684     swffont->layout->bounds = (SRECT*)rfx_calloc(sizeof(SRECT)*font->num_glyphs);
2685     swffont->encoding = FONT_ENCODING_UNICODE;
2686     swffont->numchars = font->num_glyphs;
2687     swffont->maxascii = font->max_unicode;
2688     swffont->ascii2glyph = (int*)rfx_calloc(sizeof(int)*swffont->maxascii);
2689     swffont->glyph2ascii = (U16*)rfx_calloc(sizeof(U16)*swffont->numchars);
2690     swffont->glyph = (SWFGLYPH*)rfx_calloc(sizeof(SWFGLYPH)*swffont->numchars);
2691     swffont->glyphnames = (char**)rfx_calloc(sizeof(char*)*swffont->numchars);
2692     for(t=0;t<font->max_unicode;t++) {
2693         swffont->ascii2glyph[t] = font->unicode2glyph[t];
2694     }
2695     SRECT max = {0,0,0,0};
2696     for(t=0;t<font->num_glyphs;t++) {
2697         drawer_t draw;
2698         gfxline_t*line;
2699         double advance = 0;
2700         swffont->glyph2ascii[t] = font->glyphs[t].unicode;
2701         if(swffont->glyph2ascii[t] == 0xffff || swffont->glyph2ascii[t] == 0x0000) {
2702             /* flash 8 flashtype requires unique unicode IDs for each character.
2703                We use the Unicode private user area to assign characters, hoping that
2704                the font doesn't contain more than 2048 glyphs */
2705             swffont->glyph2ascii[t] = 0xe000 + (t&0x1fff);
2706         }
2707
2708         if(font->glyphs[t].name) {
2709             swffont->glyphnames[t] = strdup(font->glyphs[t].name);
2710         } else {
2711             swffont->glyphnames[t] = 0;
2712         }
2713         advance = font->glyphs[t].advance;
2714
2715         swf_Shape01DrawerInit(&draw, 0);
2716         line = font->glyphs[t].line;
2717         while(line) {
2718             FPOINT c,to;
2719             c.x = line->sx * GLYPH_SCALE; c.y = line->sy * GLYPH_SCALE;
2720             to.x = line->x * GLYPH_SCALE; to.y = line->y * GLYPH_SCALE;
2721             if(line->type == gfx_moveTo) {
2722                 draw.moveTo(&draw, &to);
2723             } else if(line->type == gfx_lineTo) {
2724                 draw.lineTo(&draw, &to);
2725             } else if(line->type == gfx_splineTo) {
2726                 draw.splineTo(&draw, &c, &to);
2727             }
2728             line = line->next;
2729         }
2730         draw.finish(&draw);
2731         swffont->glyph[t].shape = swf_ShapeDrawerToShape(&draw);
2732
2733         SRECT bbox = swf_ShapeDrawerGetBBox(&draw);
2734         swf_ExpandRect2(&max, &bbox);
2735
2736         swffont->layout->bounds[t] = bbox;
2737             
2738         if(advance<32768.0/20) {
2739             swffont->glyph[t].advance = (int)(advance*20);
2740         } else {
2741             //msg("<warning> Advance value overflow in glyph %d", t);
2742             swffont->glyph[t].advance = 32767;
2743         }
2744
2745         draw.dealloc(&draw);
2746
2747         swf_ExpandRect2(&bounds, &swffont->layout->bounds[t]);
2748     }
2749     for(t=0;t<font->num_glyphs;t++) {
2750         SRECT bbox = swffont->layout->bounds[t];
2751
2752         /* if the glyph doesn't have a bounding box, use the
2753            combined bounding box (necessary e.g. for space characters) */
2754         if(!(bbox.xmin|bbox.ymin|bbox.xmax|bbox.ymax)) {
2755             swffont->layout->bounds[t] = bbox = max;
2756         }
2757         
2758         /* check that the advance value is reasonable, by comparing it
2759            with the bounding box */
2760         if(bbox.xmax>0 && (bbox.xmax*10 < swffont->glyph[t].advance || !swffont->glyph[t].advance)) {
2761             if(swffont->glyph[t].advance)
2762                 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);
2763             swffont->glyph[t].advance = bbox.xmax;
2764         }
2765         //swffont->glyph[t].advance = bbox.xmax - bbox.xmin;
2766     }
2767
2768
2769     /* Flash player will use the advance value from the char, and the ascent/descent values
2770        from the layout for text selection.
2771        ascent will extend the char into negative y direction, from the baseline, while descent
2772        will extend in positive y direction, also from the baseline.
2773        The baseline is defined as the y-position zero 
2774      */
2775
2776     swffont->layout->ascent = -bounds.ymin;
2777     if(swffont->layout->ascent < 0)
2778         swffont->layout->ascent = 0;
2779     swffont->layout->descent = bounds.ymax;
2780     if(swffont->layout->descent < 0)
2781         swffont->layout->descent = 0;
2782     swffont->layout->leading = bounds.ymax - bounds.ymin;
2783
2784     /* if the font has proper ascent/descent values (>0) and those define
2785        greater line spacing that what we estimated from the bounding boxes,
2786        use the font's parameters */
2787     if(font->ascent*20 > swffont->layout->ascent)
2788         swffont->layout->ascent = font->ascent*20;
2789     if(font->descent*20 > swffont->layout->descent)
2790         swffont->layout->descent = font->descent*20;
2791
2792     return swffont;
2793 }
2794
2795 static void swf_addfont(gfxdevice_t*dev, gfxfont_t*font)
2796 {
2797     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2798
2799     if(i->swffont && i->swffont->name && !strcmp((char*)i->swffont->name,font->id))
2800         return; // the requested font is the current font
2801     
2802     fontlist_t*last=0,*l = i->fontlist;
2803     while(l) {
2804         last = l;
2805         if(!strcmp((char*)l->swffont->name, font->id)) {
2806             return; // we already know this font
2807         }
2808         l = l->next;
2809     }
2810     l = (fontlist_t*)rfx_calloc(sizeof(fontlist_t));
2811     l->swffont = gfxfont_to_swffont(font, font->id);
2812     l->next = 0;
2813     if(last) {
2814         last->next = l;
2815     } else {
2816         i->fontlist = l;
2817     }
2818     swf_FontSetID(l->swffont, getNewID(i->dev));
2819
2820     if(getScreenLogLevel() >= LOGLEVEL_DEBUG)  {
2821         int iii;
2822         // print font information
2823         msg("<debug> Font %s",font->id);
2824         msg("<debug> |   ID: %d", l->swffont->id);
2825         msg("<debug> |   Version: %d", l->swffont->version);
2826         msg("<debug> |   Name: %s", l->swffont->name);
2827         msg("<debug> |   Numchars: %d", l->swffont->numchars);
2828         msg("<debug> |   Maxascii: %d", l->swffont->maxascii);
2829         msg("<debug> |   Style: %d", l->swffont->style);
2830         msg("<debug> |   Encoding: %d", l->swffont->encoding);
2831         for(iii=0; iii<l->swffont->numchars;iii++) {
2832             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, 
2833                     l->swffont->layout->bounds[iii].xmin/20.0,
2834                     l->swffont->layout->bounds[iii].ymin/20.0,
2835                     l->swffont->layout->bounds[iii].xmax/20.0,
2836                     l->swffont->layout->bounds[iii].ymax/20.0
2837                     );
2838             int t;
2839             for(t=0;t<l->swffont->maxascii;t++) {
2840                 if(l->swffont->ascii2glyph[t] == iii)
2841                     msg("<debug> | - maps to %d",t);
2842             }
2843         }
2844     }
2845 }
2846
2847 static void swf_switchfont(gfxdevice_t*dev, const char*fontid)
2848 {
2849     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2850
2851     if(i->swffont && i->swffont->name && !strcmp((char*)i->swffont->name,fontid))
2852         return; // the requested font is the current font
2853     
2854     fontlist_t*l = i->fontlist;
2855     while(l) {
2856         if(!strcmp((char*)l->swffont->name, fontid)) {
2857             i->swffont = l->swffont;
2858             return; //done!
2859         }
2860         l = l->next;
2861     }
2862     msg("<error> Unknown font id: %s", fontid);
2863     return;
2864 }
2865
2866 static void swf_drawchar(gfxdevice_t*dev, gfxfont_t*font, int glyph, gfxcolor_t*color, gfxmatrix_t*matrix)
2867 {
2868     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2869     if(!font) {
2870         msg("<error> swf_drawchar called (glyph %d) without font", glyph);
2871         return;
2872     }
2873
2874     if(i->config_drawonlyshapes) {
2875         gfxglyph_t*g = &font->glyphs[glyph];
2876         gfxline_t*line2 = gfxline_clone(g->line);
2877         gfxline_transform(line2, matrix);
2878         dev->fill(dev, line2, color);
2879         gfxline_free(line2);
2880         return;
2881     }
2882
2883     if(!i->swffont || !i->swffont->name || strcmp((char*)i->swffont->name,font->id)) // not equal to current font
2884     {
2885         /* TODO: remove the need for this (enhance getcharacterbbox so that it can cope
2886                  with multiple fonts */
2887         endtext(dev);
2888         swf_switchfont(dev, font->id); // set the current font
2889     }
2890     if(!i->swffont) {
2891         msg("<warning> swf_drawchar: Font is NULL");
2892         return;
2893     }
2894     if(glyph<0 || glyph>=i->swffont->numchars) {
2895         msg("<warning> No character %d in font %s (%d chars)", glyph, FIXNULL((char*)i->swffont->name), i->swffont->numchars);
2896         return;
2897     }
2898     
2899     setfontscale(dev, matrix->m00, matrix->m01, matrix->m10, matrix->m11, matrix->tx, matrix->ty, 0);
2900     
2901     double det = i->fontmatrix.sx/65536.0 * i->fontmatrix.sy/65536.0 - 
2902                  i->fontmatrix.r0/65536.0 * i->fontmatrix.r1/65536.0;
2903     if(fabs(det) < 0.0005) { 
2904         /* x direction equals y direction- the text is invisible */
2905         msg("<verbose> Not drawing invisible character character %d (det=%f, m=[%f %f;%f %f]\n", glyph, 
2906                 det,
2907                 i->fontmatrix.sx/65536.0, i->fontmatrix.r1/65536.0, 
2908                 i->fontmatrix.r0/65536.0, i->fontmatrix.sy/65536.0);
2909         return;
2910     }
2911
2912     /*if(i->swffont->glyph[glyph].shape->bitlen <= 16) {
2913         msg("<warning> Glyph %d in current charset (%s, %d characters) is empty", 
2914                 glyph, FIXNULL((char*)i->swffont->name), i->swffont->numchars);
2915         return 1;
2916     }*/
2917
2918     /* calculate character position with respect to the current font matrix */
2919     double s = 20 * GLYPH_SCALE / det;
2920     double px = matrix->tx - i->fontmatrix.tx/20.0;
2921     double py = matrix->ty - i->fontmatrix.ty/20.0;
2922     int x = (SCOORD)((  px * i->fontmatrix.sy/65536.0 - py * i->fontmatrix.r1/65536.0)*s);
2923     int y = (SCOORD)((- px * i->fontmatrix.r0/65536.0 + py * i->fontmatrix.sx/65536.0)*s);
2924     if(x>32767 || x<-32768 || y>32767 || y<-32768) {
2925         msg("<verbose> Moving character origin to %f %f\n", matrix->tx, matrix->ty);
2926         endtext(dev);
2927         setfontscale(dev, matrix->m00, matrix->m01, matrix->m10, matrix->m11, matrix->tx, matrix->ty, 1);
2928         /* since we just moved the char origin to the current char's position, 
2929            it now has the relative position (0,0) */
2930         x = y = 0;
2931     }
2932     
2933     if(i->shapeid>=0)
2934         endshape(dev);
2935     if(i->textid<0)
2936         starttext(dev);
2937
2938     msg("<trace> Drawing char %d in font %d at %d,%d in color %02x%02x%02x%02x", 
2939             glyph, i->swffont->id, x, y, color->r, color->g, color->b, color->a);
2940
2941     putcharacter(dev, i->swffont->id, glyph, x, y, i->current_font_size, *(RGBA*)color);
2942     swf_FontUseGlyph(i->swffont, glyph);
2943     return;
2944 }