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