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