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