├── test ├── tester ├── README.txt └── test.cpp ├── doc └── img │ ├── test.png │ └── index.png ├── library.properties ├── library.json ├── README.md ├── examples └── TFT_eSPI_Demo │ ├── TFT_eSPI_Demo.ino │ └── data │ ├── about.svg │ ├── test.svg │ └── index.svg ├── SvgOutput_TFT_eSPI.h ├── SvgParser.h └── SvgParser.cpp /test/tester: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpautsch/SvgParser/HEAD/test/tester -------------------------------------------------------------------------------- /doc/img/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpautsch/SvgParser/HEAD/doc/img/test.png -------------------------------------------------------------------------------- /doc/img/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxpautsch/SvgParser/HEAD/doc/img/index.png -------------------------------------------------------------------------------- /test/README.txt: -------------------------------------------------------------------------------- 1 | Tester for development 2 | rm tester; c++ test.cpp ../SvgParser.cpp -I./.. -o tester; ./tester -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=SvgParser 2 | version=0.0.2 3 | author=maxpautsch 4 | maintainer=maxpautsch 5 | sentence=A SVG GUI library for ESP8266. Output to a (touch) display and also as webservice 6 | paragraph=A SVG GUI library for ESP8266. Output to a (touch) display and also as webservice. Is able to handle links and provides a callback system. 7 | category=Display 8 | url=https://github.com/maxpautsch/SvgParser 9 | architectures=esp8266,esp32,stm32 10 | includes=SvgParser.h 11 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SvgParser", 3 | "version": "0.2", 4 | "keywords": "SVG, ESP8266, ESP32, GUI, ILI9341, ST7735, ILI9163, S6D02A1, ILI9486", 5 | "description": "A SVG GUI library for ESP8266. Output to a (touch) display and also as webservice", 6 | "repository": 7 | { 8 | "type": "git", 9 | "url": "https://github.com/maxpautsch/SvgParser" 10 | }, 11 | "authors": 12 | { 13 | "name": "maxpautsch", 14 | "email": "git@pautsch.eu", 15 | "maintainer": true 16 | }, 17 | "frameworks": "arduino", 18 | "platforms": "espressif8266" 19 | } 20 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "SvgParser.h" 4 | 5 | char * returnTest(int argc, char* argv[]){ 6 | // first argv is allways name of function 7 | char *output = "RET VAL for #"; 8 | char *ptr = (char *)malloc(strlen(output)+strlen(argv[0])+2); 9 | strcpy(ptr,output); 10 | strcpy(ptr+strlen(output),argv[0]); 11 | ptr[strlen(output)+strlen(argv[0])]='#'; 12 | ptr[strlen(output)+strlen(argv[0])+1]=0; 13 | return ptr; 14 | 15 | } 16 | 17 | 18 | int counter=42; 19 | 20 | char * increment(int argc, char* argv[]){ 21 | counter++; 22 | return NULL; 23 | } 24 | char * decrement(int argc, char* argv[]){ 25 | counter--; 26 | return NULL; 27 | } 28 | 29 | char * printCounter(int argc, char* argv[]){ 30 | char *ptr = (char *)malloc(10); 31 | if(ptr == NULL) return NULL; 32 | sprintf(ptr,"%i",counter); 33 | return ptr; 34 | } 35 | 36 | char * printTime(int argc, char* argv[]){ 37 | char *ptr = (char *)malloc(10); 38 | if(ptr == NULL) return NULL; 39 | sprintf(ptr,"%i",2323); 40 | return ptr; 41 | } 42 | 43 | int main() 44 | { 45 | char *link; 46 | SvgOutput svgOutput = SvgOutput(); 47 | SvgParser svg = SvgParser(&svgOutput); 48 | 49 | 50 | svg.addCallback("time",printTime); 51 | svg.addCallback("nr",printCounter); 52 | svg.addCallback("dec",decrement); 53 | svg.addCallback("inc",increment); 54 | 55 | 56 | svg.readFile((char *)"../../../TFT_eSPI_Demo/data/index.svg"); 57 | 58 | svg.print(); 59 | //svg.linkManagement(1); 60 | 61 | svg.linkManagement(); 62 | if(svg.onClick(2,420, &link)) printf("pressed: #%s#\n",link); 63 | if(svg.onClick(20,420, &link)) printf("pressed: #%s#\n",link); 64 | 65 | 66 | svg.callbackManagement(); 67 | 68 | return 0; 69 | } 70 | 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SvgParser 2 | This project implements a basic SVG parser for GUI applications on ESP8266 based devices. The GUI can be output on a display (with touch) or as a website. 3 | Currently it supports the TFT_eSPI display driver, but it can be very easily ported to other displays! 4 | 5 | The following SVG elements are supported right now: svg, rects, circles, text, tspan, g (group), path, a (link). 6 | 7 | In order to add live content to the output on the display, a callback mechanism is implemented. A specified tag is replaced by the return value of a function provided by the user program. 8 | 9 | Touch displays are supported. With them, the link system can be used. Links to other SVGs can be executed automatically. The callback system applies also to the link system. 10 | 11 | These two SVGs are used as test patterns: 12 | 13 | 14 | ![demo index page](./doc/img/index.png) 15 | 16 | In the user programm, the callbacks can be added like this: 17 | 18 | ```javascript 19 | int counter = 42; 20 | 21 | char * increment(int argc, char* argv[]) { 22 | counter++; 23 | return NULL; 24 | } 25 | char * decrement(int argc, char* argv[]) { 26 | counter--; 27 | return NULL; 28 | } 29 | 30 | char * printCounter(int argc, char* argv[]) { 31 | char *ptr = (char *)malloc(10); 32 | if (ptr == NULL) return NULL; 33 | sprintf(ptr, "%i", counter); 34 | return ptr; 35 | } 36 | 37 | ... 38 | // first, create an instance of the output class. Here the TFT_eSPI driver is used: 39 | SvgOutput_TFT_eSPI svgOutput = SvgOutput_TFT_eSPI(); 40 | // this output instance is passed to the SvgParser: 41 | SvgParser svg = SvgParser(&svgOutput); 42 | 43 | // register callbacks: 44 | svg.addCallback("nr", printCounter); 45 | svg.addCallback("dec", decrement); 46 | svg.addCallback("inc", increment); 47 | 48 | // read and output file: 49 | svg.readFile((char *)"/index.svg"); 50 | svg.print(); 51 | 52 | // here, all the work is done: 53 | for(;;){ 54 | if (tft.getTouch(&x, &y)) { 55 | if (svg.onClick(x, y, &link)) { 56 | Serial.printf("pressed: #%s#\n", link); 57 | free(link); 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | This will replace the text in the box inbetween increase and decrease. 64 | The boxes behind increase and decrease are links with href:index.svg?dec and index.svg?inc 65 | 66 | When pressing the links, the callback function is executed and the display is beeing refreshed. 67 | The value on the screen changes accordingly. 68 | 69 | The second test page is used to check the order of elements: 70 | 71 | ![demo test page](./doc/img/test.png) 72 | 73 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_Demo/TFT_eSPI_Demo.ino: -------------------------------------------------------------------------------- 1 | #include "FS.h" 2 | #include 3 | #include 4 | #include 5 | #include "SvgOutput_TFT_eSPI.h" 6 | 7 | TFT_eSPI tft = TFT_eSPI(); 8 | SvgOutput_TFT_eSPI svgOutput = SvgOutput_TFT_eSPI(); 9 | SvgParser svg = SvgParser(&svgOutput); 10 | 11 | 12 | #define CALIBRATION_FILE "/calibrationData" 13 | 14 | 15 | int counter = 42; 16 | 17 | char * increment(int argc, char* argv[]) { 18 | counter++; 19 | return NULL; 20 | } 21 | char * decrement(int argc, char* argv[]) { 22 | counter--; 23 | return NULL; 24 | } 25 | 26 | char * printCounter(int argc, char* argv[]) { 27 | char *ptr = (char *)malloc(10); 28 | if (ptr == NULL) return NULL; 29 | sprintf(ptr, "%i", counter); 30 | return ptr; 31 | } 32 | 33 | char * printTime(int argc, char* argv[]) { 34 | char *ptr = (char *)malloc(10); 35 | if (ptr == NULL) return NULL; 36 | sprintf(ptr, "%i", millis()); 37 | return ptr; 38 | } 39 | 40 | 41 | void setup(void) { 42 | uint16_t calibrationData[5]; 43 | uint8_t calDataOK = 0; 44 | 45 | Serial.begin(115200); 46 | Serial.println("starting"); 47 | 48 | tft.init(); 49 | 50 | tft.setRotation(2); 51 | tft.fillScreen((0xFFFF)); 52 | 53 | tft.setCursor(20, 0, 2); 54 | tft.setTextColor(TFT_BLACK, TFT_WHITE); tft.setTextSize(1); 55 | tft.println("calibration run"); 56 | 57 | // check file system 58 | if (!SPIFFS.begin()) { 59 | Serial.println("formating file system"); 60 | 61 | SPIFFS.format(); 62 | SPIFFS.begin(); 63 | } 64 | 65 | // check if calibration file exists 66 | if (SPIFFS.exists(CALIBRATION_FILE)) { 67 | File f = SPIFFS.open(CALIBRATION_FILE, "r"); 68 | if (f) { 69 | if (f.readBytes((char *)calibrationData, 14) == 14) 70 | calDataOK = 1; 71 | f.close(); 72 | } 73 | } 74 | if (calDataOK) { 75 | // calibration data valid 76 | calibrationData[4] ^= 0x06; 77 | tft.setTouch(calibrationData); 78 | } else { 79 | // data not valid. recalibrate 80 | tft.calibrateTouch(calibrationData, TFT_WHITE, TFT_RED, 15); 81 | calibrationData[4] ^= 0x06; 82 | tft.setTouch(calibrationData); 83 | // store data 84 | File f = SPIFFS.open(CALIBRATION_FILE, "w"); 85 | if (f) { 86 | f.write((const unsigned char *)calibrationData, 14); 87 | f.close(); 88 | } 89 | } 90 | 91 | tft.fillScreen((0xFFFF)); 92 | 93 | svg.addCallback("time", printTime); 94 | svg.addCallback("nr", printCounter); 95 | svg.addCallback("dec", decrement); 96 | svg.addCallback("inc", increment); 97 | 98 | svg.readFile((char *)"/index.svg"); 99 | 100 | svg.print(); 101 | // list links 102 | svg.linkManagement(); 103 | } 104 | 105 | 106 | 107 | void loop() { 108 | uint16_t x, y; 109 | static uint16_t color; 110 | char * link; 111 | static bool clicked = false; 112 | 113 | if (tft.getTouch(&x, &y)) { 114 | if (svg.onClick(x, y, &link)) { 115 | Serial.printf("pressed: #%s#\n", link); 116 | free(link); 117 | } 118 | } 119 | } 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /SvgOutput_TFT_eSPI.h: -------------------------------------------------------------------------------- 1 | #include "SvgParser.h" 2 | #include 3 | 4 | extern TFT_eSPI tft; 5 | 6 | class SvgOutput_TFT_eSPI : public SvgOutput 7 | { 8 | public: 9 | SvgOutput_TFT_eSPI() {}; 10 | 11 | 12 | 13 | virtual void circle(int16_t x, int16_t y, int16_t radius, struct svgStyle_t * style) 14 | { 15 | DBG_OUT("CIRCLE: x %i y %i radiuss %i stroke width: %i\n",x,y,radius,style->stroke_width); 16 | tft.drawCircle(x, y, radius, style->stroke_color); 17 | int16_t start=0, end; // radius is in middle of stroke 18 | 19 | if(style->stroke_color_set != UNSET && style->stroke_width) { 20 | uint16_t stroke_color = convertColor(style->stroke_color); 21 | 22 | start = style->stroke_width*style->x_scale/2; 23 | end = start; 24 | end += (style->stroke_width*style->x_scale -start -end); 25 | 26 | // check if this can be done by printing to filled circles 27 | if(style->fill_color_set == SET){ 28 | start = style->stroke_width*style->x_scale; 29 | tft.fillCircle(x, y, radius+end, stroke_color); 30 | } else { 31 | 32 | DBG_OUT("start: %i end: %i\n",start,end); 33 | for(uint16_t i=radius-start; ifill_color_set == SET) { 40 | tft.fillCircle(x, y, radius-start, convertColor(style->fill_color)); 41 | } 42 | 43 | } 44 | 45 | virtual void rect(int16_t x, int16_t y, int16_t width, int16_t height, struct svgStyle_t * style) 46 | { 47 | DBG_OUT("RECT: x %i y %i width %i height %i\n",x,y,width, height); 48 | 49 | // filled rect? 50 | if(style->fill_color_set == SET) { 51 | tft.fillRect(x, y, width, height, convertColor(style->fill_color)); 52 | } 53 | 54 | if(style->stroke_color_set != UNSET && style->stroke_width) { 55 | uint16_t stroke_color = convertColor(style->stroke_color); 56 | tft.drawRect(x, y, width, height, stroke_color); 57 | } 58 | } 59 | virtual void text(int16_t x, int16_t y, char * text, struct svgStyle_t * style) 60 | { 61 | DBG_OUT("TEXT: x %i y %i size %i text \"%s\"\n", x, y, style->font_size, text); 62 | if(style->stroke_color_set == UNSET && style->fill_color_set == UNSET) return; 63 | 64 | tft.setTextColor(convertColor(style->stroke_color)); 65 | tft.setTextSize(1); 66 | 67 | DBG_OUT("cur font height: %i\n",tft.fontHeight()); 68 | uint8_t newHeight = round(style->font_size*style->y_scale/ tft.fontHeight()); 69 | if(!newHeight) newHeight++; 70 | DBG_OUT("height factor: %i\n",newHeight); 71 | 72 | tft.setTextSize(newHeight); 73 | 74 | tft.setTextDatum(BL_DATUM); 75 | tft.drawString(text, x, y + (style->font_size*style->y_scale - tft.fontHeight())/2); 76 | 77 | } 78 | 79 | virtual void path(uint16_t *data, uint16_t len, struct svgStyle_t * style) 80 | { 81 | if(len<2) return; 82 | if(style->stroke_color_set == UNSET) return; 83 | uint16_t color = convertColor(style->stroke_color); 84 | 85 | for(uint16_t i = 1; i> 16, (color & 0x0000FF00) >> 8, color & 0x000000FF); 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /SvgParser.h: -------------------------------------------------------------------------------- 1 | 2 | /*************************************************************************************** 3 | * * Function name: SvgParser 4 | ** Description: Constructor 5 | ***************************************************************************************/ 6 | 7 | #ifndef _SVG_PARSER_H_ 8 | #define _SVG_PARSER_H_ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef ESP8266 15 | #include "FS.h" 16 | #define DBG_OUT Serial.printf 17 | #else 18 | #include 19 | #define DBG_OUT printf 20 | #endif 21 | 22 | 23 | #ifndef uint8_t 24 | #define uint8_t unsigned char 25 | #endif 26 | 27 | #ifndef int8_t 28 | #define int8_t char 29 | #endif 30 | 31 | #ifndef uint16_t 32 | #define uint16_t unsigned short 33 | #endif 34 | 35 | #ifndef int16_t 36 | #define int16_t short 37 | #endif 38 | 39 | #ifndef uint32_t 40 | #define uint32_t unsigned int 41 | #endif 42 | 43 | 44 | // ATTENTION: whenever you modify this line, change 'svgTypeNames' in 'processTag' accordingly! 45 | enum svgTypes_t {SVG, RECT, TEXT, TSPAN, GROUP, PATH, LINK, CIRCLE, NONE}; 46 | enum svgPropertyState_t {UNDEFINED, UNSET, SET}; 47 | 48 | struct svgStyle_t { 49 | int16_t x_offset; 50 | int16_t y_offset; 51 | int16_t x; 52 | int16_t y; 53 | float x_scale; 54 | float y_scale; 55 | 56 | struct svgLinkList_t *linkRef; 57 | 58 | uint32_t fill_color; 59 | uint32_t stroke_color; 60 | uint8_t stroke_width; 61 | uint8_t font_size; 62 | 63 | enum svgPropertyState_t fill_color_set :2; 64 | enum svgPropertyState_t stroke_color_set :2; 65 | enum svgPropertyState_t stroke_width_set :2; 66 | enum svgPropertyState_t font_size_set :2; 67 | 68 | }; 69 | 70 | // not yet dynamic 71 | #define SVG_PARSER_MAX_CALLBACK_ARGS 8 72 | 73 | struct svgCallbackList_t { 74 | char * expression; 75 | char * (*callback)(int argc, char* argv[]); 76 | struct svgCallbackList_t *next; 77 | }; 78 | 79 | struct svgLinkList_t { 80 | char * link; 81 | struct svgLinkList_t *next; 82 | }; 83 | 84 | struct svgLinkRefList_t { 85 | int16_t x_min, y_min, x_max, y_max; 86 | struct svgLinkList_t *linkRef; 87 | struct svgLinkRefList_t *next; 88 | }; 89 | 90 | 91 | class SvgOutput 92 | { 93 | public: 94 | SvgOutput() {}; 95 | 96 | virtual void circle(int16_t x, int16_t y, int16_t radius, struct svgStyle_t * style) 97 | { 98 | DBG_OUT("CIRCLE: x %i y %i radius %i\n",x,y,radius); 99 | } 100 | 101 | virtual void rect(int16_t x, int16_t y, int16_t width, int16_t height, struct svgStyle_t * style) 102 | { 103 | DBG_OUT("RECT: x %i y %i width %i height %i\n",x,y,width, height); 104 | 105 | } 106 | virtual void text(int16_t x, int16_t y, char * text, struct svgStyle_t * style) 107 | { 108 | DBG_OUT("TEXT: x %i y %i text \"%s\"\n",x,y,text); 109 | 110 | } 111 | virtual void path(uint16_t *data, uint16_t len, struct svgStyle_t * style) 112 | { 113 | DBG_OUT("PATH: len: %i \n",len); 114 | } 115 | 116 | private: 117 | 118 | }; 119 | 120 | // Class functions and variables 121 | class SvgParser 122 | 123 | { 124 | 125 | public: 126 | SvgParser(SvgOutput *newout); 127 | uint8_t print(int16_t start_x=0, int16_t start_y=0); 128 | uint8_t readFile(char * fileName); 129 | uint8_t linkManagement(uint8_t cleanup=0); 130 | uint8_t callbackManagement(uint8_t cleanup=0); 131 | uint8_t addCallback(char * expression, char * (*callback)(int argc, char* argv[])); 132 | 133 | uint8_t onClick(uint16_t x, uint16_t y, char **link); 134 | uint8_t executeCallbacks(); 135 | 136 | private: 137 | 138 | SvgOutput *_output; 139 | 140 | uint16_t _bufLen, _curPos; 141 | char * _data = NULL; 142 | 143 | int8_t * _callbackStart = "<@"; 144 | int8_t * _callbackEnd = "@>"; 145 | int8_t * _linkSplit = "@"; 146 | 147 | bool _automaticLinkManagement = true; 148 | 149 | struct svgLinkList_t * _linkList = NULL; 150 | struct svgLinkRefList_t * _linkRefList = NULL; 151 | struct svgCallbackList_t * _callbackList = NULL; 152 | 153 | 154 | void cleanup(){ 155 | // clean up link lists 156 | linkManagement(1); 157 | // callbackManagement(1); 158 | if(_data != NULL) 159 | free(_data); 160 | _data = NULL; 161 | _bufLen = 0; 162 | _curPos = 0; 163 | } 164 | 165 | void trimStr(); 166 | 167 | uint8_t processElement(char * start, enum svgTypes_t type, struct svgStyle_t * style); 168 | uint8_t processTag(char * start, char ** tagStart, uint16_t *processed, uint8_t parents, char *parentEnd, struct svgStyle_t * parentStyle); 169 | 170 | uint8_t getProperty(char * start, const char * property, float * data); 171 | uint8_t getProperty(char * start, const char * property, int16_t * data); 172 | uint8_t getProperty(char * start, const char * property, char ** data); 173 | char * getPropertyStart(char * start, const char * property); 174 | uint8_t parseStyle(char * start, struct svgStyle_t * style); 175 | 176 | uint8_t addLinkReference(int16_t x_min, int16_t y_min, int16_t x_max, int16_t y_max, struct svgStyle_t * style); 177 | char * executeCallbacks(char *programLine); 178 | uint8_t parseInCallbacks(); 179 | 180 | 181 | 182 | }; 183 | 184 | 185 | 186 | 187 | #endif 188 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_Demo/data/about.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 70 | 71 | 74 | 81 | 82 | 86 | 93 | 94 | about 105 | home 116 | test 127 | SvgParser lib 139 | https://github.com/maxpautsch/SvgParser 151 | 152 | 153 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_Demo/data/test.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 64 | 71 | 72 | 76 | 83 | 84 | 87 | 94 | 95 | about 106 | home 117 | test 128 | 135 | 142 | 149 | 156 | 162 | 168 | 174 | 180 | SvgParser 191 | 196 | 201 | 206 | 211 | 216 | 221 | SvgParser 232 | path: 245 | 252 | 259 | 260 | 261 | -------------------------------------------------------------------------------- /examples/TFT_eSPI_Demo/data/index.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 64 | 71 | 72 | 75 | 82 | 83 | 86 | 93 | 94 | about 105 | home 116 | test 127 | 134 | 141 | 148 | 155 | SvgParser 166 | 171 | 176 | 181 | 186 | 191 | 196 | 202 | 208 | 214 | 220 | 227 | callback test: <@time now@> 238 | 243 | 250 | 251 | 255 | 262 | 263 | 267 | 274 | 275 | decrement 286 | <@nr@> 297 | increment 308 | 309 | 310 | -------------------------------------------------------------------------------- /SvgParser.cpp: -------------------------------------------------------------------------------- 1 | #include "SvgParser.h" 2 | #define COMPAT_TEST 3 | 4 | #if defined(ESP8266) || defined(COMPAT_TEST) 5 | unsigned int 6 | xtoi(const char *hexStr) 7 | { 8 | unsigned int result = 0; 9 | 10 | if (hexStr) 11 | { 12 | char c; 13 | for (;;) 14 | { 15 | c = *hexStr++; 16 | if ((c >= '0') && (c <= '9')) 17 | c -= '0'; 18 | else if ((c >= 'A') && (c <= 'F')) 19 | c -= 'A' - 10; 20 | else if ((c >= 'a') && (c <= 'f')) 21 | c -= 'a' - 10; 22 | else 23 | break; 24 | result = (result * 16) + c; 25 | } 26 | } 27 | return(result); 28 | } 29 | #endif 30 | 31 | #if defined(ESP8266) 32 | extern "C" { 33 | #include "user_interface.h" 34 | } 35 | 36 | #endif 37 | 38 | SvgParser::SvgParser(SvgOutput *newout) 39 | { 40 | _output = newout; 41 | } 42 | 43 | 44 | uint8_t SvgParser::getProperty(char * start, const char * property, float * data){ 45 | char *ptr = getPropertyStart(start, property); 46 | // DBG_OUT("get float %s\n",property); 47 | if(ptr == NULL) 48 | return false; 49 | // return sscanf(ptr,"%f",data); 50 | *data = atof(ptr); 51 | return true; 52 | } 53 | 54 | uint8_t SvgParser::getProperty(char * start, const char * property, int16_t * data){ 55 | char *ptr = getPropertyStart(start, property); 56 | // DBG_OUT("get int %s\n",property); 57 | 58 | if(ptr == NULL) 59 | return false; 60 | //return sscanf(ptr,"%i",data); 61 | *data=atoi(ptr); 62 | return true; 63 | } 64 | 65 | uint8_t SvgParser::getProperty(char * start, const char * property, char ** data){ 66 | char *ptr2; 67 | char *ptr = getPropertyStart(start, property); 68 | 69 | // DBG_OUT("get char %s\n",property); 70 | 71 | if(ptr == NULL) 72 | return false; 73 | 74 | ptr2 = strstr (ptr,"\""); 75 | if(ptr2 == NULL) return false; 76 | 77 | *data = (char *)malloc((ptr2-ptr)+1); 78 | if(*data == NULL) 79 | return false; 80 | 81 | memcpy(*data,ptr,ptr2-ptr); 82 | (*data)[ptr2-ptr]=0; 83 | 84 | return true; 85 | } 86 | 87 | char * SvgParser::getPropertyStart(char * start, const char * property){ 88 | char *ptr, *ptr2, *searchString; 89 | uint16_t pLen = strlen(property); 90 | uint16_t sLen; 91 | uint8_t retVal; 92 | 93 | searchString = (char *)malloc(pLen+4); 94 | if(searchString == NULL) 95 | return NULL; 96 | memcpy(searchString+1,property,pLen); 97 | searchString[0]=' '; 98 | searchString[pLen+1]='='; 99 | searchString[pLen+2]='"'; 100 | searchString[pLen+3]=0; 101 | #if defined(ESP8266) || defined(COMPAT_TEST) 102 | ptr = strstr ((char *)start,searchString); 103 | #else 104 | ptr = strcasestr ((char *)start,searchString); 105 | #endif 106 | free(searchString); 107 | 108 | if(ptr == NULL) return NULL; 109 | return ptr+pLen+3; 110 | } 111 | 112 | uint8_t SvgParser::parseStyle(char * start, struct svgStyle_t * style){ 113 | char *ptr = start; 114 | char *ptr2; 115 | enum svgPropertyState_t tmp_state; 116 | uint32_t tmp_val; 117 | float tmp_val_f; 118 | 119 | const char *propNames[] = {"fill:", "stroke:", "stroke-width:", "font-size:", NULL}; 120 | 121 | // DBG_OUT("search style: #%s#\n",start); 122 | 123 | for(;;){ 124 | if(*ptr == 0) 125 | break; 126 | ptr2 = strstr(ptr,";"); 127 | if(ptr2) 128 | *ptr2 = 0; 129 | 130 | // DBG_OUT("style prop : %s\n",ptr); 131 | 132 | for(int8_t i=0; ;i++){ 133 | if(propNames[i] == NULL) break; 134 | #if defined(ESP8266) || defined(COMPAT_TEST) 135 | if(strncmp(ptr,propNames[i],strlen(propNames[i])) == 0){ 136 | #else 137 | if(strncasecmp(ptr,propNames[i],strlen(propNames[i])) == 0){ 138 | #endif 139 | tmp_state = SET; 140 | #if defined(ESP8266) || defined(COMPAT_TEST) 141 | if(strncmp(ptr+strlen(propNames[i]),"none",4) == 0){ 142 | 143 | #else 144 | 145 | if(strncasecmp(ptr+strlen(propNames[i]),"none",4) == 0){ 146 | #endif 147 | tmp_state = UNSET; 148 | tmp_val = 0; 149 | tmp_val_f = 0; 150 | } else if(*(ptr+strlen(propNames[i])) == '#') { 151 | #if defined(ESP8266) || defined(COMPAT_TEST) 152 | tmp_val = xtoi(ptr+strlen(propNames[i])+1); 153 | #else 154 | 155 | if(!sscanf(ptr+strlen(propNames[i])+1,"%x",&tmp_val)) 156 | return false; 157 | #endif 158 | 159 | } else if(*(ptr+strlen(propNames[i])) >= '0' && *(ptr+strlen(propNames[i])) <='9') { 160 | #if defined(ESP8266) || defined(COMPAT_TEST) 161 | tmp_val_f = atof(ptr+strlen(propNames[i])); 162 | #else 163 | if(!sscanf(ptr+strlen(propNames[i]),"%f",&tmp_val_f)) 164 | return false; 165 | #endif 166 | } 167 | 168 | switch(i) { 169 | case 0: // fill 170 | style->fill_color_set = tmp_state; 171 | style->fill_color = tmp_val; 172 | break; 173 | case 1: // stroke 174 | style->stroke_color_set = tmp_state; 175 | style->stroke_color = tmp_val; 176 | break; 177 | case 2: // stroke-width 178 | style->stroke_width_set = tmp_state; 179 | if(tmp_val_f>0 && tmp_val_f<1) tmp_val_f = 1; 180 | style->stroke_width = tmp_val_f; 181 | break; 182 | case 3: // font-size 183 | style->font_size_set = tmp_state; 184 | if(tmp_val_f>0 && tmp_val_f<1) tmp_val_f = 1; 185 | style->font_size = tmp_val_f; 186 | break; 187 | } 188 | } 189 | } 190 | 191 | if(ptr2) 192 | ptr = ptr2 +1; 193 | else 194 | *ptr = 0; 195 | } 196 | } 197 | 198 | uint8_t SvgParser::onClick(uint16_t x, uint16_t y, char **link){ 199 | 200 | struct svgLinkRefList_t ** tLinkRefList = &_linkRefList; 201 | 202 | while(*tLinkRefList != NULL){ 203 | if(x >= (*tLinkRefList)->x_min && x <= (*tLinkRefList)->x_max && y >= (*tLinkRefList)->y_min && y <= (*tLinkRefList)->y_max) { 204 | if(*link != NULL) free(link); 205 | *link = (char *)malloc(strlen(((*tLinkRefList)->linkRef)->link)); 206 | if(*link == NULL) return false; 207 | strcpy(*link,((*tLinkRefList)->linkRef)->link); 208 | 209 | if(_automaticLinkManagement){ 210 | if(((*tLinkRefList)->linkRef)->link[0] == '/'){ 211 | char *ptr = strstr(((*tLinkRefList)->linkRef)->link, _linkSplit); 212 | 213 | if(ptr != NULL){ 214 | *ptr = 0; 215 | char * drop = executeCallbacks(ptr+strlen(_linkSplit)); 216 | } 217 | if(readFile(((*tLinkRefList)->linkRef)->link)){ 218 | print(); 219 | } 220 | } 221 | 222 | } 223 | return true; 224 | } 225 | tLinkRefList = &(*tLinkRefList)->next; 226 | } 227 | return false; 228 | } 229 | 230 | uint8_t SvgParser::addCallback(char * expression, char * (*callback)(int argc, char* argv[])){ 231 | struct svgCallbackList_t ** tCallbackList = &_callbackList; 232 | 233 | while(*tCallbackList != NULL){ 234 | tCallbackList = &(*tCallbackList)->next; 235 | } 236 | *tCallbackList = (struct svgCallbackList_t *)malloc(sizeof(svgCallbackList_t)); 237 | if(*tCallbackList == NULL) 238 | return false; 239 | 240 | (*tCallbackList)->expression = (char *)malloc(strlen(expression)+1); 241 | if((*tCallbackList)->expression == NULL){ 242 | free(*tCallbackList); 243 | return false; 244 | } 245 | strcpy((*tCallbackList)->expression,expression); 246 | 247 | (*tCallbackList)->callback = callback; 248 | 249 | (*tCallbackList)->next = NULL; 250 | return true; 251 | } 252 | 253 | uint8_t SvgParser::callbackManagement(uint8_t cleanup){ 254 | if(!cleanup) DBG_OUT("callback management\n"); 255 | 256 | struct svgCallbackList_t ** tCallbackNext, ** tCallback = &_callbackList; 257 | 258 | while(*tCallback != NULL){ 259 | tCallbackNext = &(*tCallback)->next; 260 | if(cleanup){ 261 | free(((*tCallback)->expression)); 262 | free(*tCallback); 263 | *tCallback = NULL; 264 | } 265 | else{ 266 | char * argv[2]; 267 | argv[0] = (*tCallback)->expression; 268 | argv[1] = NULL; 269 | char * ptr =(*tCallback)->callback(1,argv); 270 | 271 | DBG_OUT("callback entry: #%s# returnValue: #%s#\n",(*tCallback)->expression,ptr); 272 | free(ptr); 273 | } 274 | tCallback = tCallbackNext; 275 | } 276 | } 277 | 278 | char * SvgParser::executeCallbacks(char *programLine){ 279 | char * argv[SVG_PARSER_MAX_CALLBACK_ARGS+1]; 280 | uint8_t argc=0; 281 | struct svgCallbackList_t ** tCallback; 282 | 283 | argc = 1; 284 | argv[0] = programLine; 285 | // DBG_OUT("cb tag: #%s#\n",argv[0]); 286 | for(char * ptr = argv[0]; ptr < programLine+strlen(programLine); ptr++){ 287 | if(*ptr == ' '){ 288 | if(argc < SVG_PARSER_MAX_CALLBACK_ARGS){ 289 | argv[argc] = ptr; 290 | *ptr = 0; 291 | argc++; 292 | } 293 | } 294 | } 295 | argv[argc+1] = NULL; 296 | // argc and argv are prepared. search for callback function 297 | tCallback = &_callbackList; 298 | while(*tCallback != NULL){ 299 | if (strcmp((*tCallback)->expression,argv[0])==0){ 300 | return (*tCallback)->callback(1,argv); 301 | } 302 | tCallback = &(*tCallback)->next; 303 | } 304 | 305 | return NULL; 306 | } 307 | 308 | uint8_t SvgParser::parseInCallbacks(){ 309 | 310 | char * foundStart=NULL, *foundStartNext=NULL, * foundEnd; 311 | foundStart = strstr(_data,_callbackStart); 312 | 313 | while(foundStart != NULL){ 314 | foundEnd = strstr(foundStart+strlen(_callbackStart),_callbackEnd); 315 | if(foundEnd == NULL) { 316 | //DBG_OUT("no end!\n%s\n",foundStart+strlen(_callbackStart)); 317 | return false; 318 | } 319 | foundStartNext = strstr(foundStart+strlen(_callbackStart),_callbackStart); 320 | // check if there is an end tag before the next start tag. 321 | // if not, this is not a proper callback tag! 322 | if((foundStartNext == NULL) || (foundStartNext > foundEnd)){ 323 | *foundEnd = 0; 324 | uint16_t lengthOfInsert = 0; 325 | char * ptr = executeCallbacks(foundStart+strlen(_callbackStart)); 326 | if(ptr != NULL) lengthOfInsert = strlen(ptr); 327 | 328 | // if(ptr != NULL) DBG_OUT("callback entry: #%s# returnValue: #%s#\n",foundStart,ptr); 329 | uint16_t sizeOfTag = foundEnd-foundStart+strlen(_callbackEnd); 330 | 331 | // if _data gets smaller, copy before freeing unused memory 332 | if (lengthOfInsert <= sizeOfTag){ 333 | memcpy(foundStart+lengthOfInsert,foundStart+sizeOfTag,_bufLen-sizeOfTag-(foundStart-_data)); 334 | if(ptr != NULL) 335 | memcpy(foundStart,ptr,lengthOfInsert); 336 | } 337 | 338 | if (lengthOfInsert!=foundEnd-foundStart+strlen(_callbackEnd)){ 339 | _data = (char *)realloc((void *)_data,_bufLen + lengthOfInsert - sizeOfTag); 340 | _bufLen += lengthOfInsert - sizeOfTag; 341 | if(_data == NULL) { 342 | free(ptr); 343 | return false; 344 | } 345 | } 346 | if (lengthOfInsert>sizeOfTag){ 347 | memmove(foundStart+lengthOfInsert,foundEnd+strlen(_callbackEnd),_bufLen-sizeOfTag); 348 | memcpy(foundStart,ptr,lengthOfInsert); 349 | } 350 | // next tag moved! 351 | if(foundStartNext != NULL) foundStartNext += lengthOfInsert - sizeOfTag; 352 | if(ptr != NULL) free(ptr); 353 | } 354 | foundStart = foundStartNext ; 355 | } 356 | } 357 | 358 | 359 | uint8_t SvgParser::linkManagement(uint8_t cleanup){ 360 | if(!cleanup) DBG_OUT("\nlink management\n"); 361 | 362 | // clean or print link references 363 | struct svgLinkRefList_t ** tLinkRefListNext, ** tLinkRefList = &_linkRefList; 364 | 365 | while(*tLinkRefList != NULL){ 366 | tLinkRefListNext = &(*tLinkRefList)->next; 367 | if(cleanup){ 368 | free(*tLinkRefList); 369 | *tLinkRefList = NULL; 370 | } 371 | else 372 | DBG_OUT("link reference entry: x_min %i x_max %i y_min %i y_max %i link %s\n",(*tLinkRefList)->x_min,(*tLinkRefList)->x_max,(*tLinkRefList)->y_min,(*tLinkRefList)->y_max,((*tLinkRefList)->linkRef)->link); 373 | tLinkRefList = tLinkRefListNext; 374 | } 375 | 376 | // clean or print link list 377 | struct svgLinkList_t ** tLinkListNext, ** tLinkList = &_linkList; 378 | 379 | while(*tLinkList != NULL){ 380 | tLinkListNext = &(*tLinkList)->next; 381 | if(cleanup){ 382 | free(((*tLinkList)->link)); 383 | free(*tLinkList); 384 | *tLinkList = NULL; 385 | } 386 | else 387 | DBG_OUT("link entry: #%s#\n",(*tLinkList)->link); 388 | tLinkList = tLinkListNext; 389 | } 390 | if(cleanup){ 391 | _linkList = NULL; 392 | _linkRefList = NULL; 393 | } 394 | } 395 | 396 | uint8_t SvgParser::addLinkReference(int16_t x_min, int16_t y_min, int16_t x_max, int16_t y_max, struct svgStyle_t * style){ 397 | struct svgLinkRefList_t ** tLinkRefList = &_linkRefList; 398 | 399 | if(style->linkRef == NULL) return false; 400 | 401 | while(*tLinkRefList != NULL){ 402 | tLinkRefList = &(*tLinkRefList)->next; 403 | } 404 | *tLinkRefList = (struct svgLinkRefList_t *)malloc(sizeof(svgLinkRefList_t)); 405 | if(*tLinkRefList == NULL) 406 | return false; 407 | (*tLinkRefList)->x_min = x_min; 408 | (*tLinkRefList)->y_min = y_min; 409 | (*tLinkRefList)->x_max = x_max; 410 | (*tLinkRefList)->y_max = y_max; 411 | (*tLinkRefList)->linkRef = style->linkRef; 412 | (*tLinkRefList)->next = NULL; 413 | return true; 414 | } 415 | 416 | 417 | uint8_t SvgParser::processElement(char * start, enum svgTypes_t type, struct svgStyle_t * style){ 418 | float x,y,height,width; 419 | char * styleString=NULL; 420 | // DBG_OUT("process elment type: %i \n",type); 421 | 422 | 423 | char *transform; 424 | if(getProperty(start, "transform", &transform)){ 425 | #if defined(ESP8266) || defined(COMPAT_TEST) 426 | if(strncmp(transform,"translate(",strlen("translate("))==0){ 427 | #else 428 | if(strncasecmp(transform,"translate(",strlen("translate("))==0){ 429 | #endif 430 | float x, y; 431 | #if defined(ESP8266) || defined(COMPAT_TEST) 432 | x = atof(transform+strlen("translate(")); 433 | char * tmp = strstr(transform+strlen("translate("),","); 434 | if(tmp==NULL) { 435 | free(transform); 436 | return false; 437 | } 438 | tmp++; 439 | y = atof(tmp); 440 | 441 | #else 442 | if(!sscanf(transform+strlen("translate("),"%f,%f",&x,&y)) 443 | return false; 444 | #endif 445 | 446 | // DBG_OUT("translate: %i*%i \n",x,y); 447 | style->x_offset += x* style->x_scale; 448 | style->y_offset += y* style->y_scale; 449 | } 450 | free(transform); 451 | } 452 | if(type == GROUP){ 453 | // has nothing special 454 | } 455 | else if(type == TEXT || type == TSPAN){ 456 | if(!getProperty(start, "x", &x)) 457 | return false; 458 | if(!getProperty(start, "y", &y)) 459 | return false; 460 | // check if there is a style string 461 | if(getProperty(start, "style", &styleString)) 462 | parseStyle(styleString, style); 463 | 464 | style->x = x*style->x_scale+style->x_offset; 465 | style->y = y*style->y_scale+style->y_offset; 466 | 467 | } 468 | else if(type == LINK){ 469 | char * link; 470 | uint16_t cnt = 0; 471 | 472 | struct svgLinkList_t ** tLinkList = &_linkList; 473 | 474 | if(!getProperty(start, "xlink:href", &link)) 475 | return false; 476 | // DBG_OUT("link to: #%s#\n",link); 477 | 478 | while(*tLinkList != NULL){ 479 | tLinkList = &(*tLinkList)->next; 480 | cnt ++; 481 | } 482 | *tLinkList = (struct svgLinkList_t *)malloc(sizeof(svgLinkList_t)); 483 | (*tLinkList)->link = link; 484 | (*tLinkList)->next = NULL; 485 | 486 | // all childs will get this attribute 487 | style->linkRef = *tLinkList; 488 | } 489 | else if(type == RECT){ 490 | if(!getProperty(start, "x", &x)) 491 | return false; 492 | if(!getProperty(start, "y", &y)) 493 | return false; 494 | if(!getProperty(start, "width", &width)) 495 | return false; 496 | if(!getProperty(start, "height", &height)) 497 | return false; 498 | if(!getProperty(start, "style", &styleString)) 499 | return false; 500 | x = x*style->x_scale+style->x_offset; 501 | y = y*style->y_scale+style->y_offset; 502 | height = height*style->x_scale; 503 | width = width*style->y_scale; 504 | // DBG_OUT("rect: x: %f y: %f height: %f width: %f \n",x,y,height,width); 505 | 506 | parseStyle(styleString, style); 507 | // DBG_OUT("fill: %X stroke color %X stroke width %i \n",style->fill_color, style->stroke_color,style->stroke_width); 508 | 509 | addLinkReference(x, y, x+width, y+height, style); 510 | 511 | _output->rect(x, y, width, height, style); 512 | } 513 | else if(type == CIRCLE){ 514 | float radius; 515 | if(!getProperty(start, "cx", &x)) 516 | return false; 517 | if(!getProperty(start, "cy", &y)) 518 | return false; 519 | if(!getProperty(start, "r", &radius)) 520 | return false; 521 | if(!getProperty(start, "style", &styleString)) 522 | return false; 523 | x = x*style->x_scale+style->x_offset; 524 | y = y*style->y_scale+style->y_offset; 525 | //TODO: radius scale?! 526 | radius = radius * style->x_scale; 527 | // DBG_OUT("circle: x: %f y: %f radius: %f \n",x,y,radius); 528 | 529 | parseStyle(styleString, style); 530 | // DBG_OUT("fill: %X stroke color %X stroke width %i \n",style->fill_color, style->stroke_color,style->stroke_width); 531 | 532 | addLinkReference(x-radius, y-radius, x+radius, y+radius, style); 533 | _output->circle(x, y, radius, style); 534 | 535 | } 536 | else if(type == PATH){ 537 | char * data, *ptr, *ptr2; 538 | uint8_t absolutePos = true; 539 | uint8_t first = true; 540 | float last_x, last_y; 541 | uint16_t convertedLen=0; 542 | uint16_t *converted; 543 | 544 | if(!getProperty(start, "d", &data)) 545 | return false; 546 | if(!getProperty(start, "style", &styleString)) 547 | return false; 548 | 549 | parseStyle(styleString, style); 550 | // DBG_OUT("stroke color %X stroke width %i \n",style->stroke_color,style->stroke_width); 551 | 552 | // DBG_OUT("data %s \n",data); 553 | converted = (uint16_t *)data; 554 | // the plain text is overwritten by the coordinate array 555 | ptr = data; 556 | for(;;){ 557 | if(*ptr == 0) 558 | break; 559 | ptr2 = strstr(ptr," "); 560 | if(ptr2) 561 | *ptr2 = 0; 562 | 563 | // DBG_OUT("style prop : %s\n",ptr); 564 | if(*ptr == 'M') absolutePos = true; 565 | else if (*ptr == 'm') absolutePos = false; 566 | else if (*ptr >= '0' && *ptr <= '9' || *ptr == '-'){ 567 | #if defined(ESP8266) || defined(COMPAT_TEST) 568 | x = atof(ptr); 569 | char * tmp = strstr(ptr,","); 570 | if(tmp==NULL) { 571 | free(data); 572 | return false; 573 | } 574 | tmp++; 575 | y = atof(tmp); 576 | #else 577 | if(!sscanf(ptr,"%f,%f",&x,&y)){ 578 | free(data); 579 | return false; 580 | } 581 | #endif 582 | //x *= style->x_scale; 583 | //y *= style->y_scale; 584 | 585 | if(first || absolutePos){ 586 | first = false; 587 | 588 | last_x = style->x_offset + x * (float)style->x_scale; 589 | last_y = style->y_offset + y * (float)style->x_scale; 590 | 591 | 592 | } else { 593 | last_x += x/2 * (float)style->x_scale; 594 | last_y += y/2 * (float)style->x_scale; 595 | } 596 | 597 | converted[convertedLen*2 ] = last_x; 598 | converted[convertedLen*2+1] = last_y; 599 | convertedLen++; 600 | } 601 | // ignore curves, arcs etc. 602 | 603 | if(ptr2) 604 | ptr = ptr2 +1; 605 | else 606 | *ptr = 0; 607 | } 608 | _output->path(converted, convertedLen, style); 609 | 610 | free(data); 611 | } 612 | else if(type == SVG){ 613 | char * viewbox; 614 | float viewboxData[4]; 615 | 616 | if(!getProperty(start, "width", &width)) 617 | return false; 618 | 619 | if(!getProperty(start, "height", &height)) 620 | return false; 621 | 622 | if(getProperty(start, "viewBox", &viewbox)){ 623 | #if defined(ESP8266) || defined(COMPAT_TEST) 624 | // DBG_OUT("viewbox %s\n",viewbox); 625 | viewboxData[0] = atof(viewbox); 626 | 627 | char * tmp = strstr(viewbox," "); 628 | if(tmp==NULL) return false; 629 | tmp++; 630 | viewboxData[1] = atof(tmp); 631 | 632 | tmp = strstr(tmp," "); 633 | if(tmp==NULL) return false; 634 | tmp++; 635 | viewboxData[2] = atof(tmp); 636 | 637 | tmp = strstr(tmp," "); 638 | if(tmp==NULL) return false; 639 | tmp++; 640 | viewboxData[3] = atof(tmp); 641 | 642 | // DBG_OUT("viewbox: %i %i %i %i\n",viewboxData[0],viewboxData[1],viewboxData[2],viewboxData[3]); 643 | #else 644 | 645 | if(!sscanf(viewbox,"%f %f %f %f",&viewboxData[0],&viewboxData[1],&viewboxData[2],&viewboxData[3])) 646 | return false; 647 | #endif 648 | style->x_offset = viewboxData[0]; 649 | style->y_offset = viewboxData[1]; 650 | style->x_scale = width / viewboxData[2]; 651 | style->y_scale = height / viewboxData[3]; 652 | free(viewbox); 653 | } 654 | 655 | // DBG_OUT("svg: height: %f width: %f \n",height,width); 656 | struct svgStyle_t clearStyle; 657 | memcpy(&clearStyle,style,sizeof(svgStyle_t)); 658 | clearStyle.fill_color = 0xFFFFFF; // white 659 | clearStyle.fill_color_set = SET; 660 | clearStyle.stroke_color_set = UNSET; 661 | _output->rect(style->x_offset, style->x_offset, width, height, &clearStyle); 662 | 663 | } 664 | 665 | // DBG_OUT("success: %d\n",sscanf(ptr + strlen("x=\""),"%f",&x)); 666 | if(styleString!=NULL) free(styleString); 667 | 668 | return true; 669 | } 670 | 671 | // returns stop of tag 672 | // 0: error 673 | // 1: file end 674 | // 2: parent end 675 | // 3: success 676 | uint8_t SvgParser::processTag(char * start, char ** tagStart, uint16_t *processed, uint8_t parents, char * parentEnd, struct svgStyle_t * parentStyle){ 677 | const char *svgTypeNames[] = {"svg", "rect", "text", "tspan", "g", "path", "a", "circle", NULL}; 678 | 679 | char *curPos, *thisTagEnd, *childTagStart, *searchResult, *endTag; 680 | bool endAfterTag = false; // tag is completely finished 681 | bool halfAfterTag = false; // first part is finished and > is removed 682 | bool hasChilds = false; 683 | uint16_t childProcessed; 684 | enum svgTypes_t type = NONE; 685 | struct svgStyle_t style; 686 | 687 | *processed = 0; 688 | // search for new tag 689 | for(curPos = start; curPos < _data+_bufLen-1; curPos++){ 690 | // start of new tag? 691 | if(*curPos == '<' && !(*(curPos+1)=='!' || *(curPos+1)=='?')) 692 | break; 693 | } 694 | 695 | *tagStart = curPos; 696 | 697 | // EOF? 698 | if(curPos == _data+_bufLen){ 699 | *processed = (uint16_t)(_data + _bufLen - start); 700 | return 1; 701 | } 702 | 703 | curPos++; 704 | 705 | // search for end of tag name 706 | for(searchResult = curPos; searchResult < _data+_bufLen; searchResult++){ 707 | if(*searchResult == ' ' || *searchResult == '>' || *searchResult == 0){ 708 | if(*searchResult == '>'){ 709 | halfAfterTag = true; 710 | thisTagEnd = searchResult+1; 711 | } 712 | break; 713 | } 714 | } 715 | 716 | // no end of tag name was found! 717 | if(searchResult == _data+_bufLen){ 718 | *processed = _data + _bufLen - start; 719 | return 0; 720 | } 721 | 722 | if(*(searchResult-1) == '/' || *(searchResult-1) == '?'){ 723 | endAfterTag = true; 724 | searchResult--; 725 | } 726 | 727 | *searchResult = 0; 728 | endTag = curPos-1; 729 | *endTag = '/'; 730 | 731 | 732 | if(parentEnd != NULL){ 733 | #if defined(ESP8266) || defined(COMPAT_TEST) 734 | if(strcmp(curPos,parentEnd) == 0){ 735 | #else 736 | if(strcasecmp(curPos,parentEnd) == 0){ 737 | #endif 738 | // for(int8_t i=0;i' || endAfterTag || halfAfterTag) { 777 | if(!halfAfterTag) 778 | thisTagEnd = curPos+1; 779 | halfAfterTag = true; 780 | 781 | *searchResult = ' '; 782 | *curPos = 0; 783 | 784 | processElement(searchResult, type, &style); 785 | *searchResult = 0; 786 | 787 | // completely ready with tag? 788 | if (*(curPos-1)=='/' || *(curPos-1)=='?' || endAfterTag){ // end of tag? could be removed previously 789 | *processed = curPos-start; 790 | return 3; 791 | } 792 | // search for sub tags 793 | 794 | else { 795 | // DBG_OUT("sub tag search\n"); 796 | 797 | for(;;){ 798 | uint16_t retVal = processTag(curPos,&childTagStart,&childProcessed, parents+1,endTag,&style); 799 | if(retVal == 0) 800 | return 0; 801 | else if(retVal == 1) 802 | return 1; 803 | else if(retVal == 2){ 804 | // parent end! 805 | *processed = curPos +childProcessed- start; 806 | // check for tag value widthin > < 807 | 808 | if(!hasChilds) { 809 | *childTagStart=0; 810 | *tagStart = childTagStart; 811 | if(strlen(thisTagEnd)){ 812 | if(type == TEXT || type == TSPAN){ 813 | // DBG_OUT("TEXT: x %i y %i stroke color %X stroke width %i value #%s#\n",style.x,style.y,style.stroke_color,style.stroke_width,thisTagEnd); 814 | _output->text(style.x,style.y,thisTagEnd,&style); 815 | } 816 | } 817 | } 818 | return 3; 819 | } 820 | else if(retVal == 3){ 821 | hasChilds = true; 822 | // child end. search next child 823 | curPos += childProcessed; 824 | } 825 | //*tagStart = curPos; 826 | thisTagEnd = curPos; 827 | } 828 | } 829 | } 830 | } 831 | 832 | return 0; 833 | } 834 | 835 | uint8_t SvgParser::print(int16_t start_x, int16_t start_y){ 836 | uint16_t processed; 837 | uint16_t done = 0; 838 | uint8_t retVal=0; 839 | char *childTagEnd; 840 | //uint8_t processTag(char * start, uint16_t *processed, uint8_t parents, char *parentEnd); 841 | while(done!=_bufLen){ 842 | // DBG_OUT("print run last val %i start %i\n",retVal,done); 843 | retVal = processTag(_data+done,&childTagEnd,&processed,0,NULL,NULL); 844 | if(retVal != 3) 845 | return false; 846 | 847 | done += processed; 848 | } 849 | 850 | #ifdef ESP8266 851 | DBG_OUT("free memory: %i\n",system_get_free_heap_size()); 852 | #endif 853 | return true; 854 | } 855 | 856 | 857 | void SvgParser::trimStr() { 858 | int lastCopy = 0; 859 | int startCut = 0; 860 | int pos; 861 | char *tmp; 862 | 863 | enum {NORMAL, LAST_SPACE, WITHIN_SOME_SPACES} lastWasSpace; 864 | 865 | // remove all newline, carriage return and tabs 866 | for (int i = 0; i < _bufLen; i++) { 867 | if ((_data)[i] == '\n' || (_data)[i] == '\r' || (_data)[i] == '\t') 868 | (_data)[i] = ' '; 869 | } 870 | 871 | lastWasSpace = LAST_SPACE; 872 | 873 | for (pos = 0; pos < _bufLen; pos++) { 874 | switch (lastWasSpace) { 875 | 876 | case NORMAL: 877 | if ((_data)[pos] == ' ') { 878 | lastWasSpace = LAST_SPACE; 879 | } 880 | break; 881 | 882 | case LAST_SPACE: 883 | if ((_data)[pos] == ' ') { 884 | lastWasSpace = WITHIN_SOME_SPACES; 885 | startCut = pos; 886 | } else 887 | lastWasSpace = NORMAL; 888 | break; 889 | 890 | case WITHIN_SOME_SPACES: 891 | // start cutting with first non space character 892 | if ((_data)[pos] != ' ') { 893 | memcpy(&((_data)[startCut]), &((_data)[pos]), _bufLen - pos + 1); 894 | pos = startCut; 895 | lastWasSpace = NORMAL; 896 | } 897 | break; 898 | } 899 | } 900 | 901 | // some spaces at the end? 902 | if (lastWasSpace == WITHIN_SOME_SPACES) { 903 | (_data)[startCut] = 0; 904 | } 905 | 906 | _bufLen = strlen((const char *)_data); 907 | if ((_data)[_bufLen - 1] == ' ') { 908 | (_data)[_bufLen - 1] = 0; 909 | _bufLen++; 910 | } 911 | 912 | _data = (char *)realloc((void *)_data,_bufLen); 913 | } 914 | 915 | uint8_t SvgParser::readFile(char * fileNameIn){ 916 | // as the fileName could be origin from the link system, it has to be saved before cleaning up!!! 917 | char * fileName = (char *)malloc(strlen(fileNameIn)+1); 918 | if(fileName == NULL) return false; 919 | strcpy(fileName, fileNameIn); 920 | // clean up first 921 | cleanup(); 922 | 923 | #ifdef ESP8266 924 | if (!SPIFFS.begin()) 925 | return false; 926 | 927 | // check if calibration file exists 928 | File f = SPIFFS.open(fileName, "r"); 929 | DBG_OUT("filename: #%s#\n",fileName); 930 | 931 | if (f) { 932 | _bufLen = f.size(); 933 | 934 | _data = (int8_t *)malloc(_bufLen + 1); 935 | if(_data == NULL) { 936 | f.close(); 937 | return false; 938 | } 939 | if (f.readBytes(_data, _bufLen) != _bufLen){ 940 | free(_data); 941 | f.close(); 942 | return false; 943 | } 944 | f.close(); 945 | } else return false; 946 | 947 | #else 948 | FILE *f = fopen(fileName, "rb"); 949 | fseek(f, 0, SEEK_END); 950 | long fsize = ftell(f); 951 | fseek(f, 0, SEEK_SET); //same as rewind(f); 952 | 953 | _data = (char *)malloc(fsize + 1); 954 | fread(_data, fsize, 1, f); 955 | _data[fsize] = 0; // terminate 956 | fclose(f); 957 | 958 | _bufLen = fsize; 959 | 960 | #endif 961 | _curPos = 0; 962 | //DBG_OUT("length read: %i\n",_bufLen); 963 | trimStr(); 964 | 965 | // if there are registered callbacks, parse for them in the data 966 | if(_callbackList != NULL) parseInCallbacks(); 967 | //DBG_OUT("length after trim: %i\n",_bufLen); 968 | return true; 969 | } 970 | --------------------------------------------------------------------------------