Added SVG compatiblity fixes to parser.
[swftools.git] / lib / drawer.c
1 /*  drawer.c 
2     part of swftools
3
4     A generic structure for providing vector drawing.
5     (Helper routines, spline approximation, simple text drawers)
6
7     Copyright (C) 2003 Matthias Kramm <kramm@quiss.org>
8
9     This program is free software; you can redistribute it and/or modify
10     it under the terms of the GNU General Public License as published by
11     the Free Software Foundation; either version 2 of the License, or
12     (at your option) any later version.
13
14     This program is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18
19     You should have received a copy of the GNU General Public License
20     along with this program; if not, write to the Free Software
21     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
22
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <memory.h>
27 #include <math.h>
28 #include "drawer.h"
29
30 static char* getToken(const char**p)
31 {
32     const char*start;
33     char*result;
34     while(**p && strchr(" ,\t\n\r", **p)) {
35         (*p)++;
36     } 
37     start = *p;
38     while(**p && !strchr(" ,\t\n\r", **p)) {
39         (*p)++;
40     }
41     result = malloc((*p)-start+1);
42     memcpy(result,start,(*p)-start+1);
43     result[(*p)-start] = 0;
44     return result;
45 }
46
47 void draw_conicTo(drawer_t*draw, FPOINT*  c, FPOINT*  to)
48 {
49     FPOINT* pos = &draw->pos;
50     FPOINT c1,c2;
51     c1.x = (pos->x + 2 * c->x) / 3;
52     c1.y = (pos->y + 2 * c->y) / 3;
53     c2.x = (2 * c->x + to->x) / 3;
54     c2.y = (2 * c->y + to->y) / 3;
55     draw_cubicTo(draw, &c1,&c2,to);
56
57     draw->pos = *to;
58 }
59
60 void draw_string(drawer_t*draw, const char*string)
61 {
62     const char*p = string;
63     while(*p) {
64         char*token = getToken(&p);
65         if(!token || !*token) 
66             break;
67         if(!strncmp(token, "moveTo", 6) ||
68            !strncmp(token, "M", 1) //svg
69            ) {
70             FPOINT to;
71             to.x = atoi(getToken(&p));
72             to.y = atoi(getToken(&p));
73             draw->moveTo(draw, &to);
74         }
75         else if(!strncmp(token, "lineTo", 6) ||
76                 !strncmp(token, "L", 1) //svg
77              ) {
78             FPOINT to;
79             to.x = atoi(getToken(&p));
80             to.y = atoi(getToken(&p));
81             draw->lineTo(draw, &to);
82         }
83         else if(!strncmp(token, "curveTo", 7) || !strncmp(token, "splineTo", 8)) {
84             FPOINT mid,to;
85             mid.x = atoi(getToken(&p));
86             mid.y = atoi(getToken(&p));
87             to.x = atoi(getToken(&p));
88             to.y = atoi(getToken(&p));
89             draw->splineTo(draw, &mid, &to);
90         }
91         else if(!strncmp(token, "conicTo", 5)) {
92             FPOINT mid,to;
93             mid.x = atoi(getToken(&p));
94             mid.y = atoi(getToken(&p));
95             to.x = atoi(getToken(&p));
96             to.y = atoi(getToken(&p));
97             draw_conicTo(draw, &mid, &to);
98         }
99         else if(!strncmp(token, "cubicTo", 5) ||
100                 !strncmp(token, "C", 1) //svg
101                 ) {
102             FPOINT mid1,mid2,to;
103             mid1.x = atoi(getToken(&p));
104             mid1.y = atoi(getToken(&p));
105             mid2.x = atoi(getToken(&p));
106             mid2.y = atoi(getToken(&p));
107             to.x = atoi(getToken(&p));
108             to.y = atoi(getToken(&p));
109             draw_cubicTo(draw, &mid1, &mid2, &to);
110         }
111         else if(!strncmp(token, "z", 1) //svg
112                ) {
113             // ignore
114         }
115         else    
116             fprintf(stderr, "drawer: Warning: unknown primitive '%s'\n", token);
117         
118         free(token);
119     }
120 }
121
122 struct SPLINEPOINT
123 {
124     double x,y;
125 };
126
127 struct qspline
128 {
129     struct SPLINEPOINT start;
130     struct SPLINEPOINT control;
131     struct SPLINEPOINT end;
132 };
133
134 struct cspline
135 {
136     struct SPLINEPOINT start;
137     struct SPLINEPOINT control1;
138     struct SPLINEPOINT control2;
139     struct SPLINEPOINT end;
140 };
141
142 static inline struct SPLINEPOINT cspline_getpoint(const struct cspline*s, double t)
143 {
144     struct SPLINEPOINT p;
145     double tt = t*t;
146     double ttt = tt*t;
147     double mt = (1-t);
148     double mtmt = mt*(1-t);
149     double mtmtmt = mtmt*(1-t);
150     p.x= s->end.x*ttt + 3*s->control2.x*tt*mt
151             + 3*s->control1.x*t*mtmt + s->start.x*mtmtmt;
152     p.y= s->end.y*ttt + 3*s->control2.y*tt*mt
153             + 3*s->control1.y*t*mtmt + s->start.y*mtmtmt;
154     return p;
155 }
156 static struct SPLINEPOINT qspline_getpoint(const struct qspline*s, double t)
157 {
158     struct SPLINEPOINT p;
159     p.x= s->end.x*t*t + 2*s->control.x*t*(1-t) + s->start.x*(1-t)*(1-t);
160     p.y= s->end.y*t*t + 2*s->control.y*t*(1-t) + s->start.y*(1-t)*(1-t);
161     return p;
162 }
163
164 static int approximate3(const struct cspline*s, struct qspline*q, int size, double quality2)
165 {
166     unsigned int gran = 0;
167     unsigned int istep = 0x80000000;
168     unsigned int istart = 0;
169     int num = 0;
170     int level = 0;
171     
172     while(istart<0x80000000)
173     {
174         unsigned int iend = istart + istep;
175         double start = istart/(double)0x80000000;
176         double end = iend/(double)0x80000000;
177         struct qspline test;
178         double pos,qpos;
179         char left = 0,recurse=0;
180         int t;
181         int probes = 15;
182         double dx,dy;
183
184         /* create simple approximation: a qspline which run's through the
185            qspline point at 0.5 */
186         test.start = cspline_getpoint(s, start);
187         test.control = cspline_getpoint(s, (start+end)/2);
188         test.end = cspline_getpoint(s, end);
189         /* fix the control point:
190            move it so that the new spline does runs through it */
191         test.control.x = -(test.end.x + test.start.x)/2 + 2*(test.control.x);
192         test.control.y = -(test.end.y + test.start.y)/2 + 2*(test.control.y);
193
194         /* depending on where we are in the spline, we either try to match
195            the left or right tangent */
196         if(start<0.5) 
197             left=1;
198         /* get derivative */
199         pos = left?start:end;
200         qpos = pos*pos;
201         test.control.x = s->end.x*(3*qpos) + 3*s->control2.x*(2*pos-3*qpos) + 
202                     3*s->control1.x*(1-4*pos+3*qpos) + s->start.x*(-3+6*pos-3*qpos);
203         test.control.y = s->end.y*(3*qpos) + 3*s->control2.y*(2*pos-3*qpos) + 
204                     3*s->control1.y*(1-4*pos+3*qpos) + s->start.y*(-3+6*pos-3*qpos);
205         if(left) {
206             test.control.x *= (end-start)/2;
207             test.control.y *= (end-start)/2;
208             test.control.x += test.start.x;
209             test.control.y += test.start.y;
210         } else {
211             test.control.x *= -(end-start)/2;
212             test.control.y *= -(end-start)/2;
213             test.control.x += test.end.x;
214             test.control.y += test.end.y;
215         }
216
217 #define PROBES
218 #ifdef PROBES
219         /* measure the spline's accurancy, by taking a number of probes */
220         for(t=0;t<probes;t++) {
221             struct SPLINEPOINT qr1,qr2,cr1,cr2;
222             double pos = 0.5/(probes*2)*(t*2+1);
223             double dx,dy;
224             double dist1,dist2;
225             qr1 = qspline_getpoint(&test, pos);
226             cr1 = cspline_getpoint(s, start+pos*(end-start));
227
228             dx = qr1.x - cr1.x;
229             dy = qr1.y - cr1.y;
230             dist1 = dx*dx+dy*dy;
231
232             if(dist1>quality2) {
233                 recurse=1;break;
234             }
235             qr2 = qspline_getpoint(&test, (1-pos));
236             cr2 = cspline_getpoint(s, start+(1-pos)*(end-start));
237
238             dx = qr2.x - cr2.x;
239             dy = qr2.y - cr2.y;
240             dist2 = dx*dx+dy*dy;
241
242             if(dist2>quality2) {
243                 recurse=1;break;
244             }
245         }
246 #else // quadratic error: *much* faster!
247
248         /* convert control point representation to 
249            d*x^3 + c*x^2 + b*x + a */
250
251         /* FIXME: we need to do this for the subspline between [start,end],
252            not [0,1] */
253         dx= s->end.x  - s->control2.x*3 + s->control1.x*3 - s->start.x;
254         dy= s->end.y  - s->control2.y*3 + s->control1.y*3 - s->start.y;
255         
256         /* use the integral over (f(x)-g(x))^2 between 0 and 1
257            to measure the approximation quality. 
258            (it boils down to const*d^2)
259          */
260         recurse = (dx*dx + dy*dy > quality2);
261 #endif
262
263         if(recurse && istep>1 && size-level > num) {
264             istep >>= 1;
265             level++;
266         } else {
267             *q++ = test;
268             num++;
269             istart += istep;
270             while(!(istart & istep)) {
271                 level--;
272                 istep <<= 1;
273             }
274         }
275     }
276     return num;
277 }
278
279 void draw_cubicTo(drawer_t*draw, FPOINT*  control1, FPOINT* control2, FPOINT*  to)
280 {
281     struct qspline q[128];
282     struct cspline c;
283     double quality = 80;
284     double maxerror = (500-(quality*5)>1?500-(quality*5):1)/20.0;
285     int t,num;
286
287     c.start.x = draw->pos.x;
288     c.start.y = draw->pos.y;
289     c.control1.x = control1->x;
290     c.control1.y = control1->y;
291     c.control2.x = control2->x;
292     c.control2.y = control2->y;
293     c.end.x = to->x;
294     c.end.y = to->y;
295     
296     num = approximate3(&c, q, 128, maxerror*maxerror);
297
298     for(t=0;t<num;t++) {
299         FPOINT mid;
300         FPOINT to;
301         mid.x = q[t].control.x;
302         mid.y = q[t].control.y;
303         to.x = q[t].end.x;
304         to.y = q[t].end.y;
305         draw->splineTo(draw, &mid, &to);
306     }
307 }
308
309