clip all bounding boxes to the page size
[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     
1713     msg("<trace> Placing link ID %d", buttonid);
1714     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
1715
1716     if(posx!=0 || posy!=0) {
1717         SPOINT p;
1718         p.x = (int)(posx*20);
1719         p.y = (int)(posy*20);
1720         p = swf_TurnPoint(p, &i->page_matrix);
1721         MATRIX m;
1722         m = i->page_matrix;
1723         m.tx = p.x;
1724         m.ty = p.y;
1725         swf_ObjectPlace(i->tag, buttonid, getNewDepth(dev),&m,0,0);
1726     } else {
1727         swf_ObjectPlace(i->tag, buttonid, getNewDepth(dev),&i->page_matrix,0,0);
1728     }
1729 }
1730
1731       
1732 ///////////
1733 /*
1734 for(t=0;t<picpos;t++)
1735       {
1736           if(pic_xids[t] == xid &&
1737              pic_yids[t] == yid) {
1738               width = pic_width[t];
1739               height = pic_height[t];
1740               found = t;break;
1741           }
1742       }
1743           pic_ids[picpos] = swfoutput_drawimagelosslessN(&output, pic, pal, width, height, x1,y1,x2,y2,x3,y3,x4,y4, numpalette);
1744           pic_xids[picpos] = xid;
1745           pic_yids[picpos] = yid;
1746           pic_width[picpos] = width;
1747           pic_height[picpos] = height;
1748           if(picpos<1024)
1749               picpos++;
1750             pic[width*y+x] = buf[0];
1751             xid+=x*buf[0]+1;
1752             yid+=y*buf[0]*3+1;
1753       
1754             xid += pal[1].r*3 + pal[1].g*11 + pal[1].b*17;
1755       yid += pal[1].r*7 + pal[1].g*5 + pal[1].b*23;
1756       
1757       int xid = 0;
1758       int yid = 0;
1759           xid += x*r+x*b*3+x*g*7+x*a*11;
1760           yid += y*r*3+y*b*17+y*g*19+y*a*11;
1761       int t,found = -1;
1762       for(t=0;t<picpos;t++)
1763       {
1764           if(pic_xids[t] == xid &&
1765              pic_yids[t] == yid) {
1766               found = t;break;
1767           }
1768       }
1769       if(found<0) {
1770 */
1771 ///////////
1772
1773
1774 int swf_setparameter(gfxdevice_t*dev, const char*name, const char*value)
1775 {
1776     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
1777
1778     msg("<trace> swfdevice: %s=%s", name, value);
1779     if(!strcmp(name, "jpegsubpixels")) {
1780         i->config_jpegsubpixels = atof(value);
1781     } else if(!strcmp(name, "ppmsubpixels")) {
1782         i->config_ppmsubpixels = atof(value);
1783     } else if(!strcmp(name, "subpixels")) {
1784         i->config_ppmsubpixels = i->config_jpegsubpixels = atof(value);
1785     } else if(!strcmp(name, "drawonlyshapes")) {
1786         i->config_drawonlyshapes = atoi(value);
1787     } else if(!strcmp(name, "ignoredraworder")) {
1788         i->config_ignoredraworder = atoi(value);
1789     } else if(!strcmp(name, "mark")) {
1790         if(!value || !value[0]) {
1791             if(i->mark) free(i->mark);
1792             i->mark = 0;
1793         } else {
1794             int t;
1795             i->mark = strdup("...");
1796             for(t=0;t<3;t++) if(value[t]) i->mark[t] = value[t];
1797         }
1798     } else if(!strcmp(name, "filloverlap")) {
1799         i->config_filloverlap = atoi(value);
1800     } else if(!strcmp(name, "linksopennewwindow")) {
1801         i->config_opennewwindow = atoi(value);
1802     } else if(!strcmp(name, "opennewwindow")) {
1803         i->config_opennewwindow = atoi(value);
1804     } else if(!strcmp(name, "storeallcharacters")) {
1805         i->config_storeallcharacters = atoi(value);
1806     } else if(!strcmp(name, "enablezlib")) {
1807         i->config_enablezlib = atoi(value);
1808     } else if(!strcmp(name, "bboxvars")) {
1809         i->config_bboxvars = atoi(value);
1810     } else if(!strcmp(name, "showclipshapes")) {
1811         i->config_showclipshapes = atoi(value);
1812     } else if(!strcmp(name, "reordertags")) {
1813         i->config_reordertags = atoi(value);
1814     } else if(!strcmp(name, "internallinkfunction")) {
1815         i->config_internallinkfunction = strdup(value);
1816     } else if(!strcmp(name, "externallinkfunction")) {
1817         i->config_externallinkfunction = strdup(value);
1818     } else if(!strcmp(name, "linkfunction")) { //sets both internallinkfunction and externallinkfunction
1819         i->config_internallinkfunction = strdup(value);
1820         i->config_externallinkfunction = strdup(value);
1821     } else if(!strcmp(name, "disable_polygon_conversion")) {
1822         i->config_disable_polygon_conversion = atoi(value);
1823     } else if(!strcmp(name, "normalize_polygon_positions")) {
1824         i->config_normalize_polygon_positions = atoi(value);
1825     } else if(!strcmp(name, "wxwindowparams")) {
1826         i->config_watermark = atoi(value);
1827     } else if(!strcmp(name, "insertstop")) {
1828         i->config_insertstoptag = atoi(value);
1829     } else if(!strcmp(name, "protect")) {
1830         i->config_protect = atoi(value);
1831         if(i->config_protect && i->tag) {
1832             i->tag = swf_InsertTag(i->tag, ST_PROTECT);
1833         }
1834     } else if(!strcmp(name, "flashversion")) {
1835         i->config_flashversion = atoi(value);
1836         if(i->swf) {
1837             i->swf->fileVersion = i->config_flashversion;
1838         }
1839     } else if(!strcmp(name, "framerate")) {
1840         i->config_framerate = atoi(value);
1841         if(i->swf) {
1842             i->swf->frameRate = i->config_framerate*0x100;
1843         }
1844     } else if(!strcmp(name, "minlinewidth")) {
1845         i->config_minlinewidth = atof(value);
1846     } else if(!strcmp(name, "caplinewidth")) {
1847         i->config_caplinewidth = atof(value);
1848     } else if(!strcmp(name, "linktarget")) {
1849         i->config_linktarget = strdup(value);
1850     } else if(!strcmp(name, "dumpfonts")) {
1851         i->config_dumpfonts = atoi(value);
1852     } else if(!strcmp(name, "animate")) {
1853         i->config_animate = atoi(value);
1854     } else if(!strcmp(name, "next_bitmap_is_jpeg")) {
1855         i->jpeg = 1;
1856     } else if(!strcmp(name, "jpegquality")) {
1857         int val = atoi(value);
1858         if(val<0) val=0;
1859         if(val>101) val=101;
1860         i->config_jpegquality = val;
1861     } else if(!strcmp(name, "splinequality")) {
1862         int v = atoi(value);
1863         v = 500-(v*5); // 100% = 0.25 pixel, 0% = 25 pixel
1864         if(v<1) v = 1;
1865         i->config_splinemaxerror = v;
1866     } else if(!strcmp(name, "fontquality")) {
1867         int v = atoi(value);
1868         v = 500-(v*5); // 100% = 0.25 pixel, 0% = 25 pixel
1869         if(v<1) v = 1;
1870         i->config_fontsplinemaxerror = v;
1871     } else if(!strcmp(name, "linkcolor")) {
1872         if(strlen(value)!=8) {
1873             fprintf(stderr, "Unknown format for option 'linkcolor'. (%s <-> RRGGBBAA)\n", value);
1874             return 1;
1875         }
1876 #       define NIBBLE(s) (((s)>='0' && (s)<='9')?((s)-'0'):((s)&0x0f)+9)
1877         i->config_linkcolor.r = NIBBLE(value[0])<<4 | NIBBLE(value[1]);
1878         i->config_linkcolor.g = NIBBLE(value[2])<<4 | NIBBLE(value[3]);
1879         i->config_linkcolor.b = NIBBLE(value[4])<<4 | NIBBLE(value[5]);
1880         i->config_linkcolor.a = NIBBLE(value[6])<<4 | NIBBLE(value[7]);
1881     } else if(!strcmp(name, "help")) {
1882         printf("\nSWF layer options:\n");
1883         printf("jpegdpi=<dpi>               resolution adjustment for jpeg images\n");
1884         printf("jpegsubpixels=<pixels>      resolution adjustment for jpeg images (same as jpegdpi, but in pixels)\n");
1885         printf("ppmdpi=<dpi>                resolution adjustment for lossless images\n");
1886         printf("ppmsubpixels=<pixels        resolution adjustment for  lossless images (same as ppmdpi, but in pixels)\n");
1887         printf("subpixels=<pixels>          shortcut for setting both jpegsubpixels and ppmsubpixels\n");
1888         printf("drawonlyshapes              convert everything to shapes (currently broken)\n");
1889         printf("ignoredraworder             allow to perform a few optimizations for creating smaller SWFs\n");
1890         printf("linksopennewwindow          make links open a new browser window\n");
1891         printf("linktarget                  target window name of new links\n");
1892         printf("linkcolor=<color)           color of links (format: RRGGBBAA)\n");
1893         printf("storeallcharacters          don't reduce the fonts to used characters in the output file\n");
1894         printf("enablezlib                  switch on zlib compression (also done if flashversion>=7)\n");
1895         printf("bboxvars                    store the bounding box of the SWF file in actionscript variables\n");
1896         printf("reordertags=0/1             (default: 1) perform some tag optimizations\n");
1897         printf("internallinkfunction=<name> when the user clicks a internal link (to a different page) in the converted file, this actionscript function is called\n");
1898         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");
1899         printf("disable_polygon_conversion  never convert strokes to polygons (will remove capstyles and joint styles)\n");
1900         printf("caplinewidth=<width>        the minimum thichness a line needs to have so that capstyles become visible (and are converted)\n");
1901         printf("insertstop                  put an ActionScript \"STOP\" tag in every frame\n");
1902         printf("protect                     add a \"protect\" tag to the file, to prevent loading in the Flash editor\n");
1903         printf("flashversion=<version>      the SWF fileversion (6)\n");
1904         printf("minlinewidth=<width>        convert horizontal/vertical boxes smaller than this width to lines (0.05) \n");
1905         printf("animate                     insert a showframe tag after each placeobject (animate draw order of PDF files)\n");
1906         printf("jpegquality=<quality>       set compression quality of jpeg images\n");
1907     } else {
1908         return 0;
1909     }
1910     return 1;
1911 }
1912
1913 // --------------------------------------------------------------------
1914
1915 static CXFORM gfxcxform_to_cxform(gfxcxform_t* c)
1916 {
1917     CXFORM cx;
1918     swf_GetCXForm(0, &cx, 1);
1919     if(!c)
1920         return cx;
1921     if(c->rg!=0 || c->rb!=0 || c->ra!=0 ||
1922        c->gr!=0 || c->gb!=0 || c->ga!=0 ||
1923        c->br!=0 || c->bg!=0 || c->ba!=0 ||
1924        c->ar!=0 || c->ag!=0 || c->ab!=0)
1925         msg("<warning> CXForm not SWF-compatible");
1926
1927     cx.a0 = (S16)(c->aa*256);
1928     cx.r0 = (S16)(c->rr*256);
1929     cx.g0 = (S16)(c->gg*256);
1930     cx.b0 = (S16)(c->bb*256);
1931     cx.a1 = c->ta;
1932     cx.r1 = c->tr;
1933     cx.g1 = c->tg;
1934     cx.b1 = c->tb;
1935     return cx;
1936 }
1937
1938 /* TODO */
1939 static int imageInCache(gfxdevice_t*dev, void*data, int width, int height)
1940 {
1941     return -1;
1942 }
1943 static void addImageToCache(gfxdevice_t*dev, void*data, int width, int height)
1944 {
1945 }
1946     
1947 static int add_image(swfoutput_internal*i, gfximage_t*img, int targetwidth, int targetheight, int* newwidth, int* newheight)
1948 {
1949     gfxdevice_t*dev = i->dev;
1950     RGBA*newpic = 0;
1951     RGBA*mem = (RGBA*)img->data;
1952     
1953     int sizex = img->width;
1954     int sizey = img->height;
1955     int is_jpeg = i->jpeg;
1956     i->jpeg = 0;
1957
1958     int newsizex=sizex, newsizey=sizey;
1959
1960     /// {
1961     if(is_jpeg && i->config_jpegsubpixels) {
1962         newsizex = (int)(targetwidth*i->config_jpegsubpixels + 0.5);
1963         newsizey = (int)(targetheight*i->config_jpegsubpixels + 0.5);
1964     } else if(!is_jpeg && i->config_ppmsubpixels) {
1965         newsizex = (int)(targetwidth*i->config_ppmsubpixels + 0.5);
1966         newsizey = (int)(targetheight*i->config_ppmsubpixels + 0.5);
1967     }
1968     /// }
1969
1970     if(sizex<=0 || sizey<=0)
1971         return -1;
1972     if(newsizex<=0)
1973         newsizex = 1;
1974     if(newsizey<=0)
1975         newsizey = 1;
1976
1977     /* TODO: cache images */
1978     
1979     if(newsizex<sizex || newsizey<sizey) {
1980         msg("<verbose> Scaling %dx%d image to %dx%d", sizex, sizey, newsizex, newsizey);
1981         newpic = swf_ImageScale(mem, sizex, sizey, newsizex, newsizey);
1982         *newwidth = sizex = newsizex;
1983         *newheight  = sizey = newsizey;
1984         mem = newpic;
1985     } else {
1986         *newwidth = newsizex = sizex;
1987         *newheight = newsizey  = sizey;
1988     }
1989
1990     int num_colors = swf_ImageGetNumberOfPaletteEntries(mem,sizex,sizey,0);
1991     int has_alpha = swf_ImageHasAlpha(mem,sizex,sizey);
1992     
1993     msg("<verbose> Drawing %dx%d %s%simage (id %d) at size %dx%d (%dx%d), %s%d colors",
1994             sizex, sizey, 
1995             has_alpha?(has_alpha==2?"semi-transparent ":"transparent "):"", 
1996             is_jpeg?"jpeg-":"", i->currentswfid+1,
1997             newsizex, newsizey,
1998             targetwidth, targetheight,
1999             /*newsizex, newsizey,*/
2000             num_colors>256?">":"", num_colors>256?256:num_colors);
2001
2002     /*RGBA* pal = (RGBA*)rfx_alloc(sizeof(RGBA)*num_colors);
2003     swf_ImageGetNumberOfPaletteEntries(mem,sizex,sizey,pal);
2004     int t;
2005     for(t=0;t<num_colors;t++) {
2006         printf("%02x%02x%02x%02x ",
2007                 pal[t].r, pal[t].g, pal[t].b, pal[t].a);
2008         if((t&7)==7)
2009             printf("\n");
2010     }
2011     printf("\n");*/
2012
2013     int bitid = -1;
2014     int cacheid = imageInCache(dev, mem, sizex, sizey);
2015
2016     if(cacheid<=0) {
2017         bitid = getNewID(dev);
2018         i->tag = swf_AddImage(i->tag, bitid, mem, sizex, sizey, i->config_jpegquality);
2019         addImageToCache(dev, mem, sizex, sizey);
2020     } else {
2021         bitid = cacheid;
2022     }
2023
2024     if(newpic)
2025         free(newpic);
2026     return bitid;
2027 }
2028
2029 static SRECT gfxline_getSWFbbox(gfxline_t*line)
2030 {
2031     gfxbbox_t bbox = gfxline_getbbox(line);
2032     SRECT r;
2033     r.xmin = (int)(bbox.xmin*20);
2034     r.ymin = (int)(bbox.ymin*20);
2035     r.xmax = (int)(bbox.xmax*20);
2036     r.ymax = (int)(bbox.ymax*20);
2037     return r;
2038 }
2039
2040 int line_is_empty(gfxline_t*line)
2041 {
2042     while(line) {
2043         if(line->type != gfx_moveTo)
2044             return 0;
2045         line = line->next;
2046     }
2047     return 1;
2048 }
2049
2050 static void swf_fillbitmap(gfxdevice_t*dev, gfxline_t*line, gfximage_t*img, gfxmatrix_t*matrix, gfxcxform_t*cxform)
2051 {
2052     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2053     
2054     if(line_is_empty(line))
2055         return;
2056
2057     endshape(dev);
2058     endtext(dev);
2059
2060     int targetx = (int)(sqrt(matrix->m00*matrix->m00 + matrix->m01*matrix->m01)*img->width);
2061     int targety = (int)(sqrt(matrix->m10*matrix->m10 + matrix->m11*matrix->m11)*img->height);
2062
2063     int newwidth=0,newheight=0;
2064     int bitid = add_image(i, img, targetx, targety, &newwidth, &newheight);
2065     if(bitid<0)
2066         return;
2067     double fx = (double)img->width / (double)newwidth;
2068     double fy = (double)img->height / (double)newheight;
2069
2070     MATRIX m;
2071     m.sx = (int)(65536*20*matrix->m00*fx); m.r1 = (int)(65536*20*matrix->m10*fy);
2072     m.r0 = (int)(65536*20*matrix->m01*fx); m.sy = (int)(65536*20*matrix->m11*fy);
2073     m.tx = (int)(matrix->tx*20);
2074     m.ty = (int)(matrix->ty*20);
2075   
2076     /* shape */
2077     int myshapeid = getNewID(dev);
2078     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE);
2079     SHAPE*shape;
2080     swf_ShapeNew(&shape);
2081     int fsid = swf_ShapeAddBitmapFillStyle(shape,&m,bitid,1);
2082     swf_SetU16(i->tag, myshapeid);
2083     SRECT r = gfxline_getSWFbbox(line);
2084     r = swf_ClipRect(i->pagebbox, r);
2085     swf_SetRect(i->tag,&r);
2086     swf_SetShapeStyles(i->tag,shape);
2087     swf_ShapeCountBits(shape,NULL,NULL);
2088     swf_SetShapeBits(i->tag,shape);
2089     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2090     i->swflastx = i->swflasty = UNDEFINED_COORD;
2091     drawgfxline(dev, line);
2092     swf_ShapeSetEnd(i->tag);
2093     swf_ShapeFree(shape);
2094
2095     msg("<trace> Placing image, shape ID %d, bitmap ID %d", myshapeid, bitid);
2096     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2097     CXFORM cxform2 = gfxcxform_to_cxform(cxform);
2098     swf_ObjectPlace(i->tag,myshapeid,getNewDepth(dev),&i->page_matrix,&cxform2,NULL);
2099 }
2100
2101 static RGBA col_black = {255,0,0,0};
2102
2103 static void drawoutline(gfxdevice_t*dev, gfxline_t*line)
2104 {
2105     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2106
2107     int myshapeid = getNewID(dev);
2108     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
2109
2110     SHAPE*shape;
2111     swf_ShapeNew(&shape);
2112     int lsid = swf_ShapeAddLineStyle(shape,1,&col_black);
2113
2114     swf_SetU16(i->tag,myshapeid);
2115     SRECT r = gfxline_getSWFbbox(line);
2116     r = swf_ClipRect(i->pagebbox, r);
2117     swf_SetRect(i->tag,&r);
2118     swf_SetShapeStyles(i->tag,shape);
2119     swf_ShapeCountBits(shape,NULL,NULL);
2120     swf_SetShapeBits(i->tag,shape);
2121     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,lsid,0,0);
2122     drawgfxline(dev, line);
2123     swf_ShapeSetEnd(i->tag);
2124     swf_ShapeFree(shape);
2125         
2126     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2127     swf_ObjectPlace(i->tag, myshapeid, getNewDepth(dev), 0,0,0);
2128 }
2129
2130 static void swf_startclip(gfxdevice_t*dev, gfxline_t*line)
2131 {
2132     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2133
2134     endtext(dev);
2135     endshape(dev);
2136
2137     if(i->clippos >= 127)
2138     {
2139         msg("<warning> Too many clip levels.");
2140         i->clippos --;
2141     } 
2142
2143     if(i->config_showclipshapes)
2144         drawoutline(dev, line);
2145
2146     int myshapeid = getNewID(dev);
2147     i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
2148     RGBA col;
2149     memset(&col, 0, sizeof(RGBA));
2150     col.a = 255;
2151     SHAPE*shape;
2152     swf_ShapeNew(&shape);
2153     int fsid = swf_ShapeAddSolidFillStyle(shape,&col);
2154     if(i->mark) {
2155         RGBA markcol = {0,i->mark[0],i->mark[1],i->mark[2]};
2156         swf_ShapeAddSolidFillStyle(shape,&markcol);
2157     }
2158     swf_SetU16(i->tag,myshapeid);
2159     SRECT r = gfxline_getSWFbbox(line);
2160     r = swf_ClipRect(i->pagebbox, r);
2161     swf_SetRect(i->tag,&r);
2162     swf_SetShapeStyles(i->tag,shape);
2163     swf_ShapeCountBits(shape,NULL,NULL);
2164     swf_SetShapeBits(i->tag,shape);
2165     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2166     i->swflastx = i->swflasty = UNDEFINED_COORD;
2167     i->shapeisempty = 1;
2168     drawgfxline(dev, line);
2169     if(i->shapeisempty) {
2170         /* an empty clip shape is equivalent to a shape with no area */
2171         int x = line?line->x:0;
2172         int y = line?line->y:0;
2173         moveto(dev, i->tag, x,y);
2174         lineto(dev, i->tag, x,y);
2175         lineto(dev, i->tag, x,y);
2176     }
2177     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)) {
2178         if(i->config_watermark) {
2179             gfxbbox_t r; r.xmin = r.ymin = 0;r.xmax = i->max_x;r.ymax = i->max_y;
2180             draw_watermark(dev, r, 1);
2181         }
2182     }
2183     swf_ShapeSetEnd(i->tag);
2184     swf_ShapeFree(shape);
2185
2186     /* TODO: remember the bbox, and check all shapes against it */
2187     
2188     msg("<trace> Placing clip ID %d", myshapeid);
2189     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2190     i->cliptags[i->clippos] = i->tag;
2191     i->clipshapes[i->clippos] = myshapeid;
2192     i->clipdepths[i->clippos] = getNewDepth(dev);
2193     i->clippos++;
2194 }
2195
2196 static void swf_endclip(gfxdevice_t*dev)
2197 {
2198     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2199     if(i->textid>=0)
2200         endtext(dev);
2201     if(i->shapeid>=0)
2202         endshape(dev);
2203
2204     if(!i->clippos) {
2205         msg("<error> Invalid end of clipping region");
2206         return;
2207     }
2208     i->clippos--;
2209     /*swf_ObjectPlaceClip(i->cliptags[i->clippos],i->clipshapes[i->clippos],i->clipdepths[i->clippos],&i->page_matrix,NULL,NULL,
2210             / * clip to depth: * / i->depth <= i->clipdepths[i->clippos]? i->depth : i->depth - 1);
2211     i->depth ++;*/
2212     swf_ObjectPlaceClip(i->cliptags[i->clippos],i->clipshapes[i->clippos],i->clipdepths[i->clippos],&i->page_matrix,NULL,NULL,i->depth);
2213 }
2214 static int gfxline_type(gfxline_t*line)
2215 {
2216     int tmplines=0;
2217     int tmpsplines=0;
2218     int lines=0;
2219     int splines=0;
2220     int haszerosegments=0;
2221     int length=0;
2222     while(line) {
2223         if(line->type == gfx_moveTo) {
2224             tmplines=0;
2225             tmpsplines=0;
2226         } else if(line->type == gfx_lineTo) {
2227             tmplines++;
2228             if(tmplines>lines)
2229                 lines=tmplines;
2230         } else if(line->type == gfx_splineTo) {
2231             tmpsplines++;
2232             if(tmpsplines>lines)
2233                 splines=tmpsplines;
2234         }
2235         length++;
2236         line = line->next;
2237     }
2238     if(length>400)
2239         return 5;
2240     if(lines==0 && splines==0) return 0;
2241     else if(lines==1 && splines==0) return 1;
2242     else if(lines==0 && splines==1) return 2;
2243     else if(splines==0) return 3;
2244     else return 4;
2245 }
2246
2247 static int gfxline_has_dots(gfxline_t*line)
2248 {
2249     int tmplines=0;
2250     double x=0,y=0;
2251     double dist = 0;
2252     int isline = 0;
2253     int short_gap = 0;
2254     while(line) {
2255         if(line->type == gfx_moveTo) {
2256             /* test the length of the preceding line, and assume it is a dot if
2257                it's length is less than 1.0. But *only* if there's a noticable 
2258                gap between the previous line and the next moveTo. (I've come
2259                across a PDF where thousands of "dots" were stringed together,
2260                forming a line) */
2261             int last_short_gap = short_gap;
2262             if((fabs(line->x - x) + fabs(line->y - y)) < 1.0) {
2263                 short_gap = 1;
2264             } else {
2265                 short_gap = 0;
2266             }
2267             if(isline && dist < 1 && !short_gap && !last_short_gap) {
2268                 return 1;
2269             }
2270             dist = 0;
2271             isline = 0;
2272         } else if(line->type == gfx_lineTo) {
2273             dist += fabs(line->x - x) + fabs(line->y - y);
2274             isline = 1;
2275         } else if(line->type == gfx_splineTo) {
2276             dist += fabs(line->sx - x) + fabs(line->sy - y) + 
2277                     fabs(line->x - line->sx) + fabs(line->y - line->sy);
2278             isline = 1;
2279         }
2280         x = line->x;
2281         y = line->y;
2282         line = line->next;
2283     }
2284     if(isline && dist < 1 && !short_gap) {
2285         return 1;
2286     }
2287     return 0;
2288 }
2289
2290 static int gfxline_fix_short_edges(gfxline_t*line)
2291 {
2292     double x,y;
2293     while(line) {
2294         if(line->type == gfx_lineTo) {
2295             if(fabs(line->x - x) + fabs(line->y - y) < 0.01) {
2296                 line->x += 0.01;
2297             }
2298         } else if(line->type == gfx_splineTo) {
2299             if(fabs(line->sx - x) + fabs(line->sy - y) + 
2300                fabs(line->x - line->sx) + fabs(line->y - line->sy) < 0.01) {
2301                 line->x += 0.01;
2302             }
2303         }
2304         x = line->x;
2305         y = line->y;
2306         line = line->next;
2307     }
2308     return 0;
2309 }
2310
2311 static char is_inside_page(gfxdevice_t*dev, gfxcoord_t x, gfxcoord_t y)
2312 {
2313     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2314     if(x<i->min_x || x>i->max_x) return 0;
2315     if(y<i->min_y || y>i->max_y) return 0;
2316     return 1;
2317 }
2318
2319 gfxline_t* gfxline_move(gfxline_t*line, double x, double y)
2320 {
2321     gfxline_t*l = line = gfxline_clone(line);
2322
2323     while(l) {
2324         l->x += x;
2325         l->y += y;
2326         l->sx += x;
2327         l->sy += y;
2328         l = l->next;
2329     }
2330     return line;
2331 }
2332
2333 //#define NORMALIZE_POLYGON_POSITIONS
2334
2335 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)
2336 {
2337     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2338     if(line_is_empty(line))
2339         return;
2340     int type = gfxline_type(line);
2341     int has_dots = gfxline_has_dots(line);
2342     gfxbbox_t r = gfxline_getbbox(line);
2343     int is_outside_page = !is_inside_page(dev, r.xmin, r.ymin) || !is_inside_page(dev, r.xmax, r.ymax);
2344
2345     /* TODO: * split line into segments, and perform this check for all segments */
2346
2347     if(i->config_disable_polygon_conversion || type>=5 ||
2348        (!has_dots &&
2349         (width <= i->config_caplinewidth 
2350         || (cap_style == gfx_capRound && joint_style == gfx_joinRound)
2351         || (cap_style == gfx_capRound && type<=2)))) {} else 
2352     {
2353         /* convert line to polygon */
2354         msg("<trace> draw as polygon, type=%d dots=%d", type, has_dots);
2355         if(has_dots)
2356             gfxline_fix_short_edges(line);
2357         /* we need to convert the line into a polygon */
2358         gfxpoly_t* poly = gfxpoly_strokeToPoly(line, width, cap_style, joint_style, miterLimit);
2359         gfxline_t*gfxline = gfxpoly_to_gfxline(poly);
2360         dev->fill(dev, gfxline, color);
2361         gfxline_free(gfxline);
2362         gfxpoly_free(poly);
2363         return;
2364     }
2365
2366     msg("<trace> draw as stroke, type=%d dots=%d", type, has_dots);
2367     endtext(dev);
2368
2369     if(i->config_normalize_polygon_positions) {
2370         endshape(dev);
2371         double startx = 0, starty = 0;
2372         if(line && line->type == gfx_moveTo) {
2373             startx = line->x;
2374             starty = line->y;
2375         }
2376         line = gfxline_move(line, -startx, -starty);
2377         i->shapeposx = (int)(startx*20);
2378         i->shapeposy = (int)(starty*20);
2379     }
2380
2381     swfoutput_setstrokecolor(dev, color->r, color->g, color->b, color->a);
2382     swfoutput_setlinewidth(dev, width);
2383     startshape(dev);
2384     stopFill(dev);
2385     drawgfxline(dev, line);
2386
2387     if(i->config_normalize_polygon_positions) {
2388         free(line); //account for _move
2389     }
2390
2391 }
2392
2393 static void swf_fill(gfxdevice_t*dev, gfxline_t*line, gfxcolor_t*color)
2394 {
2395     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2396     if(line_is_empty(line))
2397         return;
2398     if(!color->a)
2399         return;
2400     gfxbbox_t r = gfxline_getbbox(line);
2401     int is_outside_page = !is_inside_page(dev, r.xmin, r.ymin) || !is_inside_page(dev, r.xmax, r.ymax);
2402
2403     //if(i->chardatapos && i->chardata[i->chardatapos-1].color.a) {
2404     endtext(dev);
2405
2406     if(!i->config_ignoredraworder)
2407         endshape(dev);
2408
2409     if(i->config_normalize_polygon_positions) {
2410         endshape(dev);
2411         double startx = 0, starty = 0;
2412         if(line && line->type == gfx_moveTo) {
2413             startx = line->x;
2414             starty = line->y;
2415         }
2416         line = gfxline_move(line, -startx, -starty);
2417         i->shapeposx = (int)(startx*20);
2418         i->shapeposy = (int)(starty*20);
2419     }
2420
2421     swfoutput_setfillcolor(dev, color->r, color->g, color->b, color->a);
2422     startshape(dev);
2423     startFill(dev);
2424     i->fill=1;
2425     drawgfxline(dev, line);
2426     
2427     if(i->currentswfid==2 && r.xmin==0 && r.ymin==0 && r.xmax==i->max_x && r.ymax==i->max_y) {
2428         if(i->config_watermark) {
2429             draw_watermark(dev, r, 1);
2430         }
2431     }
2432
2433     msg("<trace> end of swf_fill (shapeid=%d)", i->shapeid);
2434
2435     if(i->config_normalize_polygon_positions) {
2436         free(line); //account for _move
2437     }
2438 }
2439
2440 static GRADIENT* gfxgradient_to_GRADIENT(gfxgradient_t*gradient)
2441 {
2442     int num = 0;
2443     gfxgradient_t*g = gradient;
2444     while(g) {
2445         num++;
2446         g = g->next;
2447     }
2448     GRADIENT* swfgradient = malloc(sizeof(GRADIENT));
2449     swfgradient->num = num;
2450     swfgradient->rgba = malloc(sizeof(swfgradient->rgba[0])*num);
2451     swfgradient->ratios = malloc(sizeof(swfgradient->ratios[0])*num);
2452
2453     g = gradient;
2454     num = 0;
2455     while(g) {
2456         swfgradient->ratios[num] = g->pos*255;
2457         swfgradient->rgba[num] = *(RGBA*)&g->color;
2458         num++;
2459         g = g->next;
2460     }
2461     return swfgradient;
2462 }
2463
2464 static void swf_fillgradient(gfxdevice_t*dev, gfxline_t*line, gfxgradient_t*gradient, gfxgradienttype_t type, gfxmatrix_t*matrix)
2465 {
2466     if(line_is_empty(line))
2467         return;
2468     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2469     
2470     if(line_is_empty(line))
2471         return;
2472
2473     GRADIENT* swfgradient = gfxgradient_to_GRADIENT(gradient);
2474     if(!swfgradient)
2475         return;
2476   
2477     endshape(dev);
2478     endtext(dev);
2479
2480     double f = type==gfxgradient_radial?4:4;
2481     MATRIX m;
2482     m.sx = (int)(matrix->m00*20*f); m.r1 = (int)(matrix->m10*20*f);
2483     m.r0 = (int)(matrix->m01*20*f); m.sy = (int)(matrix->m11*20*f);
2484     m.tx = (int)(matrix->tx*20);
2485     m.ty = (int)(matrix->ty*20);
2486
2487     /* shape */
2488     int myshapeid = getNewID(dev);
2489     i->tag = swf_InsertTag(i->tag, ST_DEFINESHAPE2);
2490     SHAPE*shape;
2491     swf_ShapeNew(&shape);
2492     int fsid = swf_ShapeAddGradientFillStyle(shape,&m,swfgradient,type==gfxgradient_radial);
2493     swf_SetU16(i->tag, myshapeid);
2494     SRECT r = gfxline_getSWFbbox(line);
2495     r = swf_ClipRect(i->pagebbox, r);
2496     swf_SetRect(i->tag,&r);
2497     swf_SetShapeStyles(i->tag,shape);
2498     swf_ShapeCountBits(shape,NULL,NULL);
2499     swf_SetShapeBits(i->tag,shape);
2500     swf_ShapeSetAll(i->tag,shape,UNDEFINED_COORD,UNDEFINED_COORD,0,fsid,0);
2501     i->swflastx = i->swflasty = UNDEFINED_COORD;
2502     drawgfxline(dev, line);
2503     swf_ShapeSetEnd(i->tag);
2504     swf_ShapeFree(shape);
2505
2506     int depth = getNewDepth(dev);
2507     msg("<trace> Placing gradient, shape ID %d, depth %d", myshapeid, depth);
2508     i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
2509     swf_ObjectPlace(i->tag,myshapeid,depth,&i->page_matrix,NULL,NULL);
2510
2511     swf_FreeGradient(swfgradient);free(swfgradient);
2512 }
2513
2514 static SWFFONT* gfxfont_to_swffont(gfxfont_t*font, const char* id)
2515 {
2516     SWFFONT*swffont = (SWFFONT*)rfx_calloc(sizeof(SWFFONT));
2517     int t;
2518     SRECT bounds = {0,0,0,0};
2519     swffont->id = -1;
2520     swffont->version = 2;
2521     swffont->name = (U8*)strdup(id);
2522     swffont->layout = (SWFLAYOUT*)rfx_calloc(sizeof(SWFLAYOUT));
2523     swffont->layout->ascent = 0; /* ? */
2524     swffont->layout->descent = 0;
2525     swffont->layout->leading = 0;
2526     swffont->layout->bounds = (SRECT*)rfx_calloc(sizeof(SRECT)*font->num_glyphs);
2527     swffont->encoding = FONT_ENCODING_UNICODE;
2528     swffont->numchars = font->num_glyphs;
2529     swffont->maxascii = font->max_unicode;
2530     swffont->ascii2glyph = (int*)rfx_calloc(sizeof(int)*swffont->maxascii);
2531     swffont->glyph2ascii = (U16*)rfx_calloc(sizeof(U16)*swffont->numchars);
2532     swffont->glyph = (SWFGLYPH*)rfx_calloc(sizeof(SWFGLYPH)*swffont->numchars);
2533     swffont->glyphnames = (char**)rfx_calloc(sizeof(char*)*swffont->numchars);
2534     for(t=0;t<font->max_unicode;t++) {
2535         swffont->ascii2glyph[t] = font->unicode2glyph[t];
2536     }
2537     for(t=0;t<font->num_glyphs;t++) {
2538         drawer_t draw;
2539         gfxline_t*line;
2540         int advance = 0;
2541         swffont->glyph2ascii[t] = font->glyphs[t].unicode;
2542         if(swffont->glyph2ascii[t] == 0xffff || swffont->glyph2ascii[t] == 0x0000) {
2543             /* flash 8 flashtype requires unique unicode IDs for each character.
2544                We use the Unicode private user area to assign characters, hoping that
2545                the font doesn't contain more than 2048 glyphs */
2546             swffont->glyph2ascii[t] = 0xe000 + (t&0x1fff);
2547         }
2548
2549         if(font->glyphs[t].name) {
2550             swffont->glyphnames[t] = strdup(font->glyphs[t].name);
2551         } else {
2552             swffont->glyphnames[t] = 0;
2553         }
2554         advance = (int)(font->glyphs[t].advance);
2555
2556         swf_Shape01DrawerInit(&draw, 0);
2557         line = font->glyphs[t].line;
2558         while(line) {
2559             FPOINT c,to;
2560             c.x = line->sx * GLYPH_SCALE; c.y = line->sy * GLYPH_SCALE;
2561             to.x = line->x * GLYPH_SCALE; to.y = line->y * GLYPH_SCALE;
2562             if(line->type == gfx_moveTo) {
2563                 draw.moveTo(&draw, &to);
2564             } else if(line->type == gfx_lineTo) {
2565                 draw.lineTo(&draw, &to);
2566             } else if(line->type == gfx_splineTo) {
2567                 draw.splineTo(&draw, &c, &to);
2568             }
2569             line = line->next;
2570         }
2571         draw.finish(&draw);
2572         swffont->glyph[t].shape = swf_ShapeDrawerToShape(&draw);
2573         swffont->layout->bounds[t] = swf_ShapeDrawerGetBBox(&draw);
2574
2575         int xmax = swffont->layout->bounds[t].xmax / 20;
2576         if(xmax>0 && xmax*2 < advance) {
2577             printf("fix bad advance value: bbox=%d, advance=%d (%f)\n", xmax, advance, font->glyphs[t].advance);
2578             advance = xmax;
2579         }
2580             
2581         if(advance<32768/20) {
2582             swffont->glyph[t].advance = advance*20;
2583         } else {
2584             swffont->glyph[t].advance = 32767;
2585         }
2586
2587         draw.dealloc(&draw);
2588
2589         swf_ExpandRect2(&bounds, &swffont->layout->bounds[t]);
2590     }
2591     if(bounds.ymin < 0 && bounds.ymax > 0) {
2592         swffont->layout->ascent = -bounds.ymin;
2593         swffont->layout->descent = bounds.ymax;
2594         swffont->layout->leading = bounds.ymax - bounds.ymin;
2595     } else {
2596         swffont->layout->ascent = (bounds.ymax - bounds.ymin)/2;
2597         swffont->layout->descent = (bounds.ymax - bounds.ymin)/2;
2598         swffont->layout->leading = bounds.ymax - bounds.ymin;
2599     }
2600     swffont->layout->descent= (bounds.ymax - bounds.ymin);
2601     swffont->layout->ascent = 0;
2602     swffont->layout->leading = bounds.ymax - bounds.ymin;
2603
2604     return swffont;
2605 }
2606
2607 static void swf_addfont(gfxdevice_t*dev, gfxfont_t*font)
2608 {
2609     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2610
2611     if(i->swffont && i->swffont->name && !strcmp((char*)i->swffont->name,font->id))
2612         return; // the requested font is the current font
2613     
2614     fontlist_t*last=0,*l = i->fontlist;
2615     while(l) {
2616         last = l;
2617         if(!strcmp((char*)l->swffont->name, font->id)) {
2618             return; // we already know this font
2619         }
2620         l = l->next;
2621     }
2622     l = (fontlist_t*)rfx_calloc(sizeof(fontlist_t));
2623     l->swffont = gfxfont_to_swffont(font, font->id);
2624     l->next = 0;
2625     if(last) {
2626         last->next = l;
2627     } else {
2628         i->fontlist = l;
2629     }
2630     swf_FontSetID(l->swffont, getNewID(i->dev));
2631
2632     if(getScreenLogLevel() >= LOGLEVEL_DEBUG)  {
2633         int iii;
2634         // print font information
2635         msg("<debug> Font %s",font->id);
2636         msg("<debug> |   ID: %d", l->swffont->id);
2637         msg("<debug> |   Version: %d", l->swffont->version);
2638         msg("<debug> |   Name: %s", l->swffont->name);
2639         msg("<debug> |   Numchars: %d", l->swffont->numchars);
2640         msg("<debug> |   Maxascii: %d", l->swffont->maxascii);
2641         msg("<debug> |   Style: %d", l->swffont->style);
2642         msg("<debug> |   Encoding: %d", l->swffont->encoding);
2643         for(iii=0; iii<l->swffont->numchars;iii++) {
2644             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, 
2645                     l->swffont->layout->bounds[iii].xmin/20.0,
2646                     l->swffont->layout->bounds[iii].ymin/20.0,
2647                     l->swffont->layout->bounds[iii].xmax/20.0,
2648                     l->swffont->layout->bounds[iii].ymax/20.0
2649                     );
2650             int t;
2651             for(t=0;t<l->swffont->maxascii;t++) {
2652                 if(l->swffont->ascii2glyph[t] == iii)
2653                     msg("<debug> | - maps to %d",t);
2654             }
2655         }
2656     }
2657 }
2658
2659 static void swf_switchfont(gfxdevice_t*dev, const char*fontid)
2660 {
2661     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2662
2663     if(i->swffont && i->swffont->name && !strcmp((char*)i->swffont->name,fontid))
2664         return; // the requested font is the current font
2665     
2666     fontlist_t*l = i->fontlist;
2667     while(l) {
2668         if(!strcmp((char*)l->swffont->name, fontid)) {
2669             i->swffont = l->swffont;
2670             return; //done!
2671         }
2672         l = l->next;
2673     }
2674     msg("<error> Unknown font id: %s", fontid);
2675     return;
2676 }
2677
2678 static void swf_drawchar(gfxdevice_t*dev, gfxfont_t*font, int glyph, gfxcolor_t*color, gfxmatrix_t*matrix)
2679 {
2680     swfoutput_internal*i = (swfoutput_internal*)dev->internal;
2681     if(!font) {
2682         msg("<error> swf_drawchar called (glyph %d) without font", glyph);
2683         return;
2684     }
2685     if(!i->swffont || !i->swffont->name || strcmp((char*)i->swffont->name,font->id)) // not equal to current font
2686     {
2687         /* TODO: remove the need for this (enhance getcharacterbbox so that it can cope
2688                  with multiple fonts */
2689         endtext(dev);
2690         swf_switchfont(dev, font->id); // set the current font
2691     }
2692     if(!i->swffont) {
2693         msg("<warning> swf_drawchar: Font is NULL");
2694         return;
2695     }
2696     if(glyph<0 || glyph>=i->swffont->numchars) {
2697         msg("<warning> No character %d in font %s (%d chars)", glyph, FIXNULL((char*)i->swffont->name), i->swffont->numchars);
2698         return;
2699     }
2700     
2701     setfontscale(dev, matrix->m00, matrix->m01, matrix->m10, matrix->m11, matrix->tx, matrix->ty, 0);
2702     
2703     double det = i->fontmatrix.sx/65536.0 * i->fontmatrix.sy/65536.0 - 
2704                  i->fontmatrix.r0/65536.0 * i->fontmatrix.r1/65536.0;
2705     if(fabs(det) < 0.0005) { 
2706         /* x direction equals y direction- the text is invisible */
2707         msg("<verbose> Not drawing invisible character character %d (det=%f, m=[%f %f;%f %f]\n", glyph, 
2708                 det,
2709                 i->fontmatrix.sx/65536.0, i->fontmatrix.r1/65536.0, 
2710                 i->fontmatrix.r0/65536.0, i->fontmatrix.sy/65536.0);
2711         return;
2712     }
2713
2714     /*if(i->swffont->glyph[glyph].shape->bitlen <= 16) {
2715         msg("<warning> Glyph %d in current charset (%s, %d characters) is empty", 
2716                 glyph, FIXNULL((char*)i->swffont->name), i->swffont->numchars);
2717         return 1;
2718     }*/
2719
2720     /* calculate character position with respect to the current font matrix */
2721     double s = 20 * GLYPH_SCALE / det;
2722     double px = matrix->tx - i->fontmatrix.tx/20.0;
2723     double py = matrix->ty - i->fontmatrix.ty/20.0;
2724     int x = (SCOORD)((  px * i->fontmatrix.sy/65536.0 - py * i->fontmatrix.r1/65536.0)*s);
2725     int y = (SCOORD)((- px * i->fontmatrix.r0/65536.0 + py * i->fontmatrix.sx/65536.0)*s);
2726     if(x>32767 || x<-32768 || y>32767 || y<-32768) {
2727         msg("<verbose> Moving character origin to %f %f\n", matrix->tx, matrix->ty);
2728         endtext(dev);
2729         setfontscale(dev, matrix->m00, matrix->m01, matrix->m10, matrix->m11, matrix->tx, matrix->ty, 1);
2730     }
2731     
2732     if(i->shapeid>=0)
2733         endshape(dev);
2734     if(i->textid<0)
2735         starttext(dev);
2736
2737     msg("<trace> Drawing char %d in font %d at %d,%d in color %02x%02x%02x%02x", 
2738             glyph, i->swffont->id, x, y, color->r, color->g, color->b, color->a);
2739
2740     putcharacter(dev, i->swffont->id, glyph, x, y, i->current_font_size, *(RGBA*)color);
2741     swf_FontUseGlyph(i->swffont, glyph);
2742     return;
2743 }