1 /*
2   Copyright Chris Jones 2020.
3   Distributed under the Boost Software License, Version 1.0.
4   See accompanying file Licence.txt or copy at...
5   https://www.boost.org/LICENSE_1_0.txt
6 */
7 
8 module readpath;
9 
10 import std.math : pow;
11 
12 /*
13   read path data, handles just the d="...." data from an svg file. I manualy
14   copy that path data out into a seperate file and thats what this handles. 
15   Saves having to mess about with the svg dom.
16   
17   path data is passed in "txt"
18   appends path data to "path"
19   T must implement moveTo, lineTo, quadTo, cubicTo and close
20   returns remaing text 
21 */
22 
23 string readPathData(T)(string txt, ref T path)
24 {
25     Scanner s;
26     s.init(txt);
27     double x,lx,y,ly = 0;
28     bool first = true;
29     char op = 0;
30 
31     if (s.skipws.popFront != 'd') throw new Exception("Bad path data");
32     if (s.skipws.popFront != '=') throw new Exception("Bad path data");
33     if (s.skipws.popFront != '\"') throw new Exception("Bad path data");
34 
35     while(!s.isEmpty)
36     {
37         s.skipws();
38 
39         switch(s.front())
40         {
41             case 'm','M','l','L','v','V','h','H','Q','q','C','c','z','Z','\"':
42                 op = s.popFront();
43                 break;
44             default:
45         }
46         
47         switch(op)
48         {
49             case 'm':
50                 x += s.skipws.readDouble();
51                 y += s.skipws.skip(',').skipws.readDouble();
52                 path.moveTo(x,y);
53                 if (first) { lx = x; ly = y; first = false; }
54                 op = 'l';
55                 break;
56             case 'M':
57                 x = s.skipws.readDouble();
58                 y = s.skipws.skip(',').skipws.readDouble();
59                 path.moveTo(x,y);
60                 if (first) { lx = x; ly = y; first = false; }
61                 op = 'L';
62                 break;
63             case 'l':
64                 x += s.skipws.readDouble();
65                 y += s.skipws.skip(',').skipws.readDouble();
66                 path.lineTo(x,y);
67                 if (first) { lx = x; ly = y; first = false; }
68                 break;
69             case 'L':
70                 x = s.skipws.readDouble();
71                 y = s.skipws.skip(',').skipws.readDouble();
72                 path.lineTo(x,y);
73                 if (first) { lx = x; ly = y; first = false; }
74                 break;
75             case 'h':
76                 x += s.skipws.readDouble();
77                 path.lineTo(x,y);
78                 if (first) { lx = x; ly = y; first = false; }
79                 break;
80             case 'H':
81                 x = s.skipws.readDouble();
82                 path.lineTo(x,y);
83                 if (first) { lx = x; ly = y; first = false; }
84                 break;
85             case 'v':
86                 y += s.skipws.readDouble();
87                 path.lineTo(x,y);
88                 if (first) { lx = x; ly = y; first = false; }
89                 break;
90             case 'V':
91                 y = s.skipws.readDouble();
92                 path.lineTo(x,y);
93                 if (first) { lx = x; ly = y; first = false; }
94                 break;
95             case 'q':
96                 float ax = x + s.skipws.readDouble();
97                 float ay = y + s.skipws.skip(',').skipws.readDouble();
98                 x = ax + s.skipws.skip(',').skipws.readDouble();
99                 y = ay + s.skipws.skip(',').skipws.readDouble();
100                 path.quadTo(ax,ay,x,y);
101                 if (first) { lx = x; ly = y; first = false; }
102                 break;
103             case 'Q':
104                 float ax = s.skipws.readDouble();
105                 float ay = s.skipws.skip(',').skipws.readDouble();
106                 x = s.skipws.skip(',').skipws.readDouble();
107                 y = s.skipws.skip(',').skipws.readDouble();
108                 path.quadTo(ax,ay,x,y);
109                 if (first) { lx = x; ly = y; first = false; }
110                 break;
111             case 'c':
112                 float ax = x + s.skipws.readDouble();
113                 float ay = x + s.skipws.skip(',').skipws.readDouble();
114                 float bx = ax + s.skipws.readDouble();
115                 float by = ay + s.skipws.skip(',').skipws.readDouble();
116                 x = bx + s.skipws.skip(',').skipws.readDouble();
117                 y = by + s.skipws.skip(',').skipws.readDouble();
118                 path.cubicTo(ax,ay,bx,by,x,y);
119                 if (first) { lx = x; ly = y; first = false; }
120                 break;
121             case 'C':
122                 float ax = s.skipws.readDouble();
123                 float ay = s.skipws.skip(',').skipws.readDouble();
124                 float bx = s.skipws.readDouble();
125                 float by = s.skipws.skip(',').skipws.readDouble();
126                 x = s.skipws.skip(',').skipws.readDouble();
127                 y = s.skipws.skip(',').skipws.readDouble();
128                 path.cubicTo(ax,ay,bx,by,x,y);
129                 if (first) { lx = x; ly = y; first = false; }
130                 break;
131             case 'z','Z':
132                 path.close();
133                 x = lx;
134                 y = ly;
135                 first = true;
136                 break;
137             case '\"':
138                 return s.text(); // return whats left
139             default:
140                 throw new Exception("Bad path data:");
141         }
142     }
143     throw new Exception("Bad path data:");
144 }
145 
146 /* 
147   little helper class for scanning text
148 */
149 
150 struct Scanner
151 {
152     immutable(char)* ptr,end;
153 
154     void init(string text)
155     {
156         ptr = text.ptr;
157         end = ptr+text.length;
158     }
159 
160     string text()
161     {
162         return ptr[0..end-ptr];
163     }
164 
165     bool isEmpty()
166     {
167         return (ptr >= end);
168     }
169 
170     // returns null if no more chars 
171 
172     char popFront()
173     {
174         if (ptr < end) return *ptr++;
175         return 0;
176     }
177 
178     // returns null if no more chars 
179 
180     char front()
181     {
182         if (ptr < end) return *ptr;
183         return 0;
184     }
185 
186     bool isNumber()
187     {
188         return ((ptr < end) && (*ptr >= '0') && (*ptr <= '9'));
189     }
190 
191     bool isAlpha()
192     {
193         return ((ptr < end) && (((*ptr | 32) >= 'a') && ((*ptr | 32) <= 'z')));
194     }
195 
196     // skip multiple whitespace 
197 
198     ref Scanner skipws()
199     {
200         while ((ptr < end) && 
201             ((*ptr == ' ') || (*ptr == '\t') || (*ptr == '\n') || (*ptr == '\r'))
202             ) ptr++;
203         return this;
204     }
205 
206     // skip single char 
207 
208     ref Scanner skip(char c)
209     {
210         if ((ptr < end) && (*ptr == c)) ptr++;
211         return this;
212     }
213 
214     // skip single char from list in 'what' 
215 
216     ref Scanner skip(string what)()
217     {
218         static foreach(c; what)
219         {
220             if (*ptr == c) { ptr++; return this; }
221         }
222         return this;
223     }
224 
225     double readDouble()
226     {
227         if (isEmpty) throw new Exception("Error reading double.");
228 
229         bool negative = (*ptr == '-');
230         if ((*ptr == '-') || (*ptr == '+')) ptr++;
231 
232         if (!isNumber) throw new Exception("Error reading double.");
233 
234         double num = *ptr - '0';
235         ptr++;
236 
237         while (isNumber)
238         {
239             num = num*10 + (*ptr) - '0';
240             ptr++;
241         }
242 
243         double fracdigits = 0;
244 
245         if ((ptr < end) && (*ptr == '.'))
246         {
247             ptr++;
248             if (!isNumber) throw new Exception("Error reading double.");
249 
250             while (isNumber)
251             {
252                 num = num*10 + (*ptr)-'0';
253                 fracdigits += 1;
254                 ptr++;
255             }
256         }
257 
258         if ((ptr < end) && ((*ptr == 'E') || (*ptr == 'e')))
259         {
260             ptr++;
261             if (isEmpty) throw new Exception("Error reading double.");
262             bool xpneg = (*ptr == '-');
263             if ((*ptr == '-') || (*ptr == '+')) ptr++;
264 
265             if (!isNumber) throw new Exception("Error reading double.");
266             double xp = (*ptr) - '0';
267             ptr++;
268 
269             while (isNumber)
270             {
271                 xp = xp*10 + (*ptr) - '0';
272                 ptr++;
273             }
274 
275             fracdigits = (xpneg) ? fracdigits+xp : fracdigits-xp;
276         }
277        
278         return ((negative) ? -num : num) * pow(10.0, -fracdigits);
279     }
280 }
281