| File: | svgtiny_parse.c |
| Warning: | line 358, column 14 Value stored to 'idend' during its initialization is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* | |||
| 2 | * This file is part of Libsvgtiny | |||
| 3 | * Licensed under the MIT License, | |||
| 4 | * http://opensource.org/licenses/mit-license.php | |||
| 5 | * Copyright 2024 Vincent Sanders <vince@netsurf-browser.org> | |||
| 6 | */ | |||
| 7 | ||||
| 8 | #include <stddef.h> | |||
| 9 | #include <math.h> | |||
| 10 | #include <float.h> | |||
| 11 | #include <string.h> | |||
| 12 | ||||
| 13 | ||||
| 14 | #include "svgtiny.h" | |||
| 15 | #include "svgtiny_internal.h" | |||
| 16 | #include "svgtiny_parse.h" | |||
| 17 | ||||
| 18 | /* Source file generated by `gperf`. */ | |||
| 19 | #include "autogenerated_colors.c" | |||
| 20 | ||||
| 21 | /** xlink XML namespace https://www.w3.org/TR/xlink11/#att-method */ | |||
| 22 | #define XLINK_NS"http://www.w3.org/1999/xlink" "http://www.w3.org/1999/xlink" | |||
| 23 | ||||
| 24 | #define SIGNIFICAND_MAX100000000 100000000 | |||
| 25 | #define EXPONENT_MAX38 38 | |||
| 26 | #define EXPONENT_MIN-38 -38 | |||
| 27 | ||||
| 28 | ||||
| 29 | /** | |||
| 30 | * parse text string into a float | |||
| 31 | * | |||
| 32 | * \param[in] text string to parse | |||
| 33 | * \param[in,out] textend On input is the end of the string in \a text. Output | |||
| 34 | * is set to indicate the character after those used to | |||
| 35 | * parse the number. | |||
| 36 | * \param[out] value The resulting value from the parse | |||
| 37 | * \return svgtiny_OK and the value updated else svgtiny_SVG_ERROR and value | |||
| 38 | * left unchanged. The textend is always updated to indicate the last | |||
| 39 | * character parsed. | |||
| 40 | * | |||
| 41 | * A number is started by 0 (started by sign) or more spaces (0x20), tabs (0x09), | |||
| 42 | * carridge returns (0xD) and newlines (0xA) followed by a decimal number. | |||
| 43 | * A number is defined as https://www.w3.org/TR/css-syntax-3/#typedef-number-token | |||
| 44 | * | |||
| 45 | * This state machine parses number text into a sign, significand and exponent | |||
| 46 | * then builds a single precision float from those values. | |||
| 47 | * | |||
| 48 | * The significand stores the first nine decimal digits of the number (floats | |||
| 49 | * only have seven thus ensuring nothing is lost in conversion). | |||
| 50 | * | |||
| 51 | * The exponent is limited to 10^38 (again the float limit) and results in | |||
| 52 | * FLT_MAX being returned with a range error. | |||
| 53 | * | |||
| 54 | * An exponent below 10^-38 will result in emitting the smallest value possible | |||
| 55 | * FLT_MIN with a range error. | |||
| 56 | * | |||
| 57 | * This is not a strtof clone because it has an input length limit instead of | |||
| 58 | * needing null terminated input, is not locale dependent and only processes | |||
| 59 | * decimal numbers (not hex etc.). These limitations are necessary to process | |||
| 60 | * the input correctly. | |||
| 61 | */ | |||
| 62 | svgtiny_code | |||
| 63 | svgtiny_parse_number(const char *text, const char **textend, float *value) | |||
| 64 | { | |||
| 65 | const char *cur; /* text cursor */ | |||
| 66 | enum { | |||
| 67 | STATE_WHITESPACE, /* processing whitespace */ | |||
| 68 | STATE_NUMBER, /* processing whole number */ | |||
| 69 | STATE_FRACT, /* processing fractional part */ | |||
| 70 | STATE_SIGNEXPONENT, /* processing exponent part */ | |||
| 71 | STATE_EXPONENT, /* processing exponent part have seen sign */ | |||
| 72 | } state = STATE_WHITESPACE; | |||
| 73 | enum b10sign { | |||
| 74 | SPOSITIVE, | |||
| 75 | SNEGATIVE, | |||
| 76 | }; | |||
| 77 | enum b10sign sign = SPOSITIVE; /* sign of number being constructed */ | |||
| 78 | unsigned int significand = 0; /* significand of number being constructed */ | |||
| 79 | int exponent = 0; /* exponent of the significand (distinct from exponent part) */ | |||
| 80 | enum b10sign exp_sign = SPOSITIVE; /* sign of exponent part */ | |||
| 81 | unsigned int exp_value = 0; /* value of the exponent part */ | |||
| 82 | unsigned int digit_count = 0; /* has an actual digit been seen */ | |||
| 83 | ||||
| 84 | ||||
| 85 | for (cur = text; cur < (*textend); cur++) { | |||
| 86 | switch (state) { | |||
| 87 | case STATE_WHITESPACE: | |||
| 88 | switch (*cur) { | |||
| 89 | case 0x9: case 0xA: case 0xD: case 0x20: | |||
| 90 | /* skip whitespace */ | |||
| 91 | continue; | |||
| 92 | ||||
| 93 | case '.': | |||
| 94 | /* new number with fraction part */ | |||
| 95 | digit_count = 0; | |||
| 96 | state = STATE_FRACT; | |||
| 97 | continue; | |||
| 98 | ||||
| 99 | case '-': | |||
| 100 | sign = SNEGATIVE; | |||
| 101 | digit_count = 0; | |||
| 102 | state = STATE_NUMBER; | |||
| 103 | continue; | |||
| 104 | ||||
| 105 | case '+': | |||
| 106 | digit_count = 0; | |||
| 107 | state = STATE_NUMBER; | |||
| 108 | continue; | |||
| 109 | ||||
| 110 | case '0': case '1': case '2': case '3': case '4': | |||
| 111 | case '5': case '6': case '7': case '8': case '9': | |||
| 112 | significand = (*cur - '0'); | |||
| 113 | digit_count = 1; | |||
| 114 | state = STATE_NUMBER; | |||
| 115 | continue; | |||
| 116 | ||||
| 117 | default: | |||
| 118 | /* anything else completes conversion */ | |||
| 119 | goto svgtiny_parse_number_end; | |||
| 120 | } | |||
| 121 | break; | |||
| 122 | ||||
| 123 | case STATE_NUMBER: | |||
| 124 | switch(*cur) { | |||
| 125 | case '.': | |||
| 126 | state = STATE_FRACT; | |||
| 127 | continue; | |||
| 128 | ||||
| 129 | case '0': case '1': case '2': case '3': case '4': | |||
| 130 | case '5': case '6': case '7': case '8': case '9': | |||
| 131 | digit_count += 1; | |||
| 132 | if (significand < SIGNIFICAND_MAX100000000) { | |||
| 133 | /* still space to acumulate digits in the significand */ | |||
| 134 | significand = (significand * 10) + (*cur - '0'); | |||
| 135 | } else { | |||
| 136 | /* significand has accumulated all the | |||
| 137 | * digits it can so just extend the | |||
| 138 | * exponent */ | |||
| 139 | exponent += 1; | |||
| 140 | } | |||
| 141 | continue; | |||
| 142 | ||||
| 143 | case 'e': | |||
| 144 | case 'E': | |||
| 145 | if (digit_count == 0) { | |||
| 146 | /* number has no digits before exponent which is a syntax error */ | |||
| 147 | goto svgtiny_parse_number_end; | |||
| 148 | ||||
| 149 | } | |||
| 150 | state = STATE_SIGNEXPONENT; | |||
| 151 | continue; | |||
| 152 | ||||
| 153 | default: | |||
| 154 | /* anything else completes conversion */ | |||
| 155 | goto svgtiny_parse_number_end; | |||
| 156 | } | |||
| 157 | ||||
| 158 | break; | |||
| 159 | ||||
| 160 | case STATE_FRACT: | |||
| 161 | switch(*cur) { | |||
| 162 | case '0': case '1': case '2': case '3': case '4': | |||
| 163 | case '5': case '6': case '7': case '8': case '9': | |||
| 164 | digit_count += 1; | |||
| 165 | if (significand < SIGNIFICAND_MAX100000000) { | |||
| 166 | /* still space to acumulate digits in the significand */ | |||
| 167 | significand = (significand * 10) + (*cur - '0'); | |||
| 168 | exponent -= 1; | |||
| 169 | } | |||
| 170 | ||||
| 171 | continue; | |||
| 172 | ||||
| 173 | case 'e': | |||
| 174 | case 'E': | |||
| 175 | if (digit_count == 0) { | |||
| 176 | /* number has no digits before exponent which is a syntax error */ | |||
| 177 | goto svgtiny_parse_number_end; | |||
| 178 | ||||
| 179 | } | |||
| 180 | state = STATE_SIGNEXPONENT; | |||
| 181 | continue; | |||
| 182 | ||||
| 183 | default: | |||
| 184 | /* anything else completes conversion */ | |||
| 185 | goto svgtiny_parse_number_end; | |||
| 186 | ||||
| 187 | } | |||
| 188 | break; | |||
| 189 | ||||
| 190 | case STATE_SIGNEXPONENT: | |||
| 191 | switch(*cur) { | |||
| 192 | case '-': | |||
| 193 | exp_sign = SNEGATIVE; | |||
| 194 | state = STATE_EXPONENT; | |||
| 195 | continue; | |||
| 196 | ||||
| 197 | case '+': | |||
| 198 | state = STATE_EXPONENT; | |||
| 199 | continue; | |||
| 200 | ||||
| 201 | case '0': case '1': case '2': case '3': case '4': | |||
| 202 | case '5': case '6': case '7': case '8': case '9': | |||
| 203 | if (exp_value < 1000) { | |||
| 204 | /* still space to acumulate digits in the exponent value */ | |||
| 205 | exp_value = (exp_value * 10) + (*cur - '0'); | |||
| 206 | } | |||
| 207 | state = STATE_EXPONENT; | |||
| 208 | continue; | |||
| 209 | ||||
| 210 | default: | |||
| 211 | /* anything else completes conversion */ | |||
| 212 | goto svgtiny_parse_number_end; | |||
| 213 | ||||
| 214 | } | |||
| 215 | break; | |||
| 216 | ||||
| 217 | case STATE_EXPONENT: | |||
| 218 | switch(*cur) { | |||
| 219 | case '0': case '1': case '2': case '3': case '4': | |||
| 220 | case '5': case '6': case '7': case '8': case '9': | |||
| 221 | if (exp_value < 1000) { | |||
| 222 | /* still space to acumulate digits in the exponent value */ | |||
| 223 | exp_value = (exp_value * 10) + (*cur - '0'); | |||
| 224 | } | |||
| 225 | ||||
| 226 | continue; | |||
| 227 | ||||
| 228 | default: | |||
| 229 | /* anything else completes conversion */ | |||
| 230 | goto svgtiny_parse_number_end; | |||
| 231 | ||||
| 232 | } | |||
| 233 | break; | |||
| 234 | } | |||
| 235 | } | |||
| 236 | ||||
| 237 | svgtiny_parse_number_end: | |||
| 238 | *textend = cur; | |||
| 239 | ||||
| 240 | if (state == STATE_WHITESPACE) { | |||
| 241 | /* no characters except whitespace */ | |||
| 242 | return svgtiny_SVG_ERROR; | |||
| 243 | } | |||
| 244 | ||||
| 245 | if (digit_count == 0) { | |||
| 246 | /* number had no digits (only +-.) which is a syntax error */ | |||
| 247 | return svgtiny_SVG_ERROR; | |||
| 248 | } | |||
| 249 | ||||
| 250 | /* deal with exponent value */ | |||
| 251 | if (exp_sign == SNEGATIVE) { | |||
| 252 | exponent -= exp_value; | |||
| 253 | } else { | |||
| 254 | exponent += exp_value; | |||
| 255 | } | |||
| 256 | ||||
| 257 | /* deal with number too large to represent */ | |||
| 258 | if (exponent > EXPONENT_MAX38) { | |||
| 259 | if (sign == SPOSITIVE) { | |||
| 260 | *value = FLT_MAX3.40282347e+38F; | |||
| 261 | } else { | |||
| 262 | *value = -FLT_MAX3.40282347e+38F; | |||
| 263 | } | |||
| 264 | return svgtiny_OK; | |||
| 265 | /*return svgtiny_RANGE;*/ | |||
| 266 | } | |||
| 267 | ||||
| 268 | /* deal with number too small to represent */ | |||
| 269 | if (exponent < EXPONENT_MIN-38) { | |||
| 270 | if (sign == SPOSITIVE) { | |||
| 271 | *value = FLT_MIN1.17549435e-38F; | |||
| 272 | } else { | |||
| 273 | *value = -FLT_MIN1.17549435e-38F; | |||
| 274 | } | |||
| 275 | return svgtiny_OK; | |||
| 276 | /*return svgtiny_RANGE;*/ | |||
| 277 | } | |||
| 278 | ||||
| 279 | if (sign == SPOSITIVE) { | |||
| 280 | *value = (float)significand * powf(10, exponent); | |||
| 281 | } else { | |||
| 282 | *value = -(float)significand * powf(10, exponent); | |||
| 283 | } | |||
| 284 | ||||
| 285 | return svgtiny_OK; | |||
| 286 | } | |||
| 287 | ||||
| 288 | ||||
| 289 | /** | |||
| 290 | * advance across hexidecimal characters | |||
| 291 | * | |||
| 292 | * \param cursor current cursor | |||
| 293 | * \param textend end of buffer | |||
| 294 | */ | |||
| 295 | static inline void advance_hex(const char **cursor, const char *textend) | |||
| 296 | { | |||
| 297 | while ((*cursor) < textend) { | |||
| 298 | if (((**cursor < 0x30 /* 0 */) || (**cursor > 0x39 /* 9 */)) && | |||
| 299 | ((**cursor < 0x41 /* A */) || (**cursor > 0x46 /* F */)) && | |||
| 300 | ((**cursor < 0x61 /* a */) || (**cursor > 0x66 /* f */))) { | |||
| 301 | break; | |||
| 302 | } | |||
| 303 | (*cursor)++; | |||
| 304 | } | |||
| 305 | } | |||
| 306 | ||||
| 307 | ||||
| 308 | /** | |||
| 309 | * advance over SVG id | |||
| 310 | * | |||
| 311 | * \param cursor current cursor | |||
| 312 | * \param textend end of buffer | |||
| 313 | * | |||
| 314 | * https://www.w3.org/TR/SVG2/struct.html#IDAttribute | |||
| 315 | */ | |||
| 316 | static inline void advance_id(const char **cursor, const char *textend) | |||
| 317 | { | |||
| 318 | while((*cursor) < textend) { | |||
| 319 | if ((**cursor == 0x20) || | |||
| 320 | (**cursor == 0x09) || | |||
| 321 | (**cursor == 0x0A) || | |||
| 322 | (**cursor == 0x0D) || | |||
| 323 | (**cursor == ')')) { | |||
| 324 | break; | |||
| 325 | } | |||
| 326 | (*cursor)++; | |||
| 327 | } | |||
| 328 | } | |||
| 329 | ||||
| 330 | static inline void advance_property_name(const char **cursor, const char *textend) | |||
| 331 | { | |||
| 332 | while ((*cursor) < textend) { | |||
| 333 | if (((**cursor < 0x30 /* 0 */) || (**cursor > 0x39 /* 9 */)) && | |||
| 334 | ((**cursor < 0x41 /* A */) || (**cursor > 0x5A /* Z */)) && | |||
| 335 | ((**cursor < 0x61 /* a */) || (**cursor > 0x7A /* z */)) && | |||
| 336 | (**cursor != '-') && | |||
| 337 | (**cursor != '_')) { | |||
| 338 | break; | |||
| 339 | } | |||
| 340 | (*cursor)++; | |||
| 341 | } | |||
| 342 | } | |||
| 343 | ||||
| 344 | ||||
| 345 | /** | |||
| 346 | * parse text to obtain identifier from a url with a fragment | |||
| 347 | * | |||
| 348 | * This limits url links to identifiers within the document. | |||
| 349 | */ | |||
| 350 | static inline svgtiny_code | |||
| 351 | parse_url_fragment(const char **text, | |||
| ||||
| 352 | const char *textend, | |||
| 353 | const char **idout, | |||
| 354 | int *idlenout) | |||
| 355 | { | |||
| 356 | const char *cursor = *text; | |||
| 357 | const char *idstart = cursor; | |||
| 358 | const char *idend = cursor; | |||
| ||||
| 359 | ||||
| 360 | advance_whitespace(&cursor, textend); | |||
| 361 | ||||
| 362 | if (*cursor != '#') { | |||
| 363 | /* not a fragment id */ | |||
| 364 | return svgtiny_SVG_ERROR; | |||
| 365 | } | |||
| 366 | cursor++; | |||
| 367 | ||||
| 368 | idstart = cursor; | |||
| 369 | advance_id(&cursor, textend); | |||
| 370 | idend = cursor; | |||
| 371 | ||||
| 372 | if ((idend - idstart) == 0) { | |||
| 373 | /* no id */ | |||
| 374 | return svgtiny_SVG_ERROR; | |||
| 375 | } | |||
| 376 | ||||
| 377 | advance_whitespace(&cursor, textend); | |||
| 378 | ||||
| 379 | /* url syntax is correct update output */ | |||
| 380 | *text = cursor; | |||
| 381 | *idout = idstart; | |||
| 382 | *idlenout = idend - idstart; | |||
| 383 | ||||
| 384 | return svgtiny_OK; | |||
| 385 | } | |||
| 386 | ||||
| 387 | ||||
| 388 | /** | |||
| 389 | * get an element in the document from a url | |||
| 390 | */ | |||
| 391 | static svgtiny_code | |||
| 392 | element_from_url(const char **url, | |||
| 393 | size_t urllen, | |||
| 394 | struct svgtiny_parse_state *state, | |||
| 395 | dom_element **element) | |||
| 396 | { | |||
| 397 | svgtiny_code res; | |||
| 398 | dom_exception exc; | |||
| 399 | const char *cursor = *url; | |||
| 400 | const char *urlend = *url + urllen; | |||
| 401 | const char *id; | |||
| 402 | int idlen = 0; | |||
| 403 | dom_string *id_str; | |||
| 404 | ||||
| 405 | /** | |||
| 406 | * \todo deal with illegal references (circular etc) | |||
| 407 | * https://svgwg.org/svg2-draft/linking.html#TermInvalidReference | |||
| 408 | * | |||
| 409 | * \todo parsing the fragment out of the url only implements same | |||
| 410 | * document references from fragments and might be extended. | |||
| 411 | * https://svgwg.org/svg2-draft/linking.html#TermSameDocumentURL | |||
| 412 | */ | |||
| 413 | res = parse_url_fragment(&cursor, urlend, &id, &idlen); | |||
| 414 | if (res != svgtiny_OK) { | |||
| 415 | return res; | |||
| 416 | } | |||
| 417 | ||||
| 418 | exc = dom_string_create_interned((const uint8_t *)id, idlen, &id_str); | |||
| 419 | if (exc != DOM_NO_ERR) { | |||
| 420 | return svgtiny_LIBDOM_ERROR; | |||
| 421 | } | |||
| 422 | ||||
| 423 | exc = dom_document_get_element_by_id(state->document, id_str, element)dom_document_get_element_by_id((dom_document *) (state->document ), (id_str), (struct dom_element **) (element)); | |||
| 424 | dom_string_unref(id_str); | |||
| 425 | if (exc != DOM_NO_ERR) { | |||
| 426 | return svgtiny_LIBDOM_ERROR; | |||
| 427 | } | |||
| 428 | ||||
| 429 | *url = cursor; | |||
| 430 | return svgtiny_OK; | |||
| 431 | } | |||
| 432 | ||||
| 433 | ||||
| 434 | enum transform_type { | |||
| 435 | TRANSFORM_UNK, | |||
| 436 | TRANSFORM_MATRIX, | |||
| 437 | TRANSFORM_TRANSLATE, | |||
| 438 | TRANSFORM_SCALE, | |||
| 439 | TRANSFORM_ROTATE, | |||
| 440 | TRANSFORM_SKEWX, | |||
| 441 | TRANSFORM_SKEWY, | |||
| 442 | }; | |||
| 443 | ||||
| 444 | ||||
| 445 | static inline svgtiny_code | |||
| 446 | apply_transform(enum transform_type transform, | |||
| 447 | int paramc, | |||
| 448 | float *paramv, | |||
| 449 | struct svgtiny_transformation_matrix *tm) | |||
| 450 | { | |||
| 451 | /* initialise matrix to cartesian standard basis | |||
| 452 | * | 1 0 0 | | |||
| 453 | * | 0 1 0 | | |||
| 454 | * | 0 0 1 | | |||
| 455 | */ | |||
| 456 | float a = 1, b = 0, c = 0, d = 1, e = 0, f = 0; /* parameter matrix */ | |||
| 457 | float za,zb,zc,zd,ze,zf; /* temporary matrix */ | |||
| 458 | float angle; | |||
| 459 | ||||
| 460 | /* there must be at least one parameter */ | |||
| 461 | if (paramc < 1) { | |||
| 462 | return svgtiny_SVG_ERROR; | |||
| 463 | } | |||
| 464 | ||||
| 465 | switch (transform) { | |||
| 466 | case TRANSFORM_MATRIX: | |||
| 467 | if (paramc != 6) { | |||
| 468 | /* too few parameters */ | |||
| 469 | return svgtiny_SVG_ERROR; | |||
| 470 | } | |||
| 471 | a=paramv[0]; | |||
| 472 | b=paramv[1]; | |||
| 473 | c=paramv[2]; | |||
| 474 | d=paramv[3]; | |||
| 475 | e=paramv[4]; | |||
| 476 | f=paramv[5]; | |||
| 477 | break; | |||
| 478 | ||||
| 479 | case TRANSFORM_TRANSLATE: | |||
| 480 | e = paramv[0]; | |||
| 481 | if (paramc == 2) { | |||
| 482 | f = paramv[1]; | |||
| 483 | } | |||
| 484 | break; | |||
| 485 | ||||
| 486 | case TRANSFORM_SCALE: | |||
| 487 | a = d = paramv[0]; | |||
| 488 | if (paramc == 2) { | |||
| 489 | d = paramv[1]; | |||
| 490 | } | |||
| 491 | break; | |||
| 492 | ||||
| 493 | case TRANSFORM_ROTATE: | |||
| 494 | angle = paramv[0] / 180 * M_PI3.14159265358979323846; | |||
| 495 | a = cos(angle); | |||
| 496 | b = sin(angle); | |||
| 497 | c = -sin(angle); | |||
| 498 | d = cos(angle); | |||
| 499 | ||||
| 500 | if (paramc == 3) { | |||
| 501 | e = -paramv[1] * cos(angle) + | |||
| 502 | paramv[2] * sin(angle) + | |||
| 503 | paramv[1]; | |||
| 504 | f = -paramv[1] * sin(angle) - | |||
| 505 | paramv[2] * cos(angle) + | |||
| 506 | paramv[2]; | |||
| 507 | } else if (paramc == 2) { | |||
| 508 | /* one or three paramters only*/ | |||
| 509 | return svgtiny_SVG_ERROR; | |||
| 510 | } | |||
| 511 | ||||
| 512 | break; | |||
| 513 | ||||
| 514 | case TRANSFORM_SKEWX: | |||
| 515 | angle = paramv[0] / 180 * M_PI3.14159265358979323846; | |||
| 516 | c = tan(angle); | |||
| 517 | break; | |||
| 518 | ||||
| 519 | case TRANSFORM_SKEWY: | |||
| 520 | angle = paramv[0] / 180 * M_PI3.14159265358979323846; | |||
| 521 | b = tan(angle); | |||
| 522 | break; | |||
| 523 | ||||
| 524 | default: | |||
| 525 | /* unknown transform (not be possible to be here) */ | |||
| 526 | return svgtiny_SVG_ERROR; | |||
| 527 | } | |||
| 528 | ||||
| 529 | za = tm->a * a + tm->c * b; | |||
| 530 | zb = tm->b * a + tm->d * b; | |||
| 531 | zc = tm->a * c + tm->c * d; | |||
| 532 | zd = tm->b * c + tm->d * d; | |||
| 533 | ze = tm->a * e + tm->c * f + tm->e; | |||
| 534 | zf = tm->b * e + tm->d * f + tm->f; | |||
| 535 | ||||
| 536 | tm->a = za; | |||
| 537 | tm->b = zb; | |||
| 538 | tm->c = zc; | |||
| 539 | tm->d = zd; | |||
| 540 | tm->e = ze; | |||
| 541 | tm->f = zf; | |||
| 542 | ||||
| 543 | return svgtiny_OK; | |||
| 544 | } | |||
| 545 | ||||
| 546 | ||||
| 547 | /* determine transform function */ | |||
| 548 | static inline svgtiny_code | |||
| 549 | parse_transform_function(const char **cursor, | |||
| 550 | const char *textend, | |||
| 551 | enum transform_type *transformout) | |||
| 552 | { | |||
| 553 | const char *tokstart; | |||
| 554 | size_t toklen; | |||
| 555 | enum transform_type transform = TRANSFORM_UNK; | |||
| 556 | ||||
| 557 | tokstart = *cursor; | |||
| 558 | while ((*cursor) < textend) { | |||
| 559 | if ((**cursor != 0x61 /* a */) && | |||
| 560 | (**cursor != 0x65 /* e */) && | |||
| 561 | (**cursor != 0x74 /* t */) && | |||
| 562 | (**cursor != 0x73 /* s */) && | |||
| 563 | (**cursor != 0x72 /* r */) && | |||
| 564 | (**cursor != 0x6B /* k */) && | |||
| 565 | (**cursor != 0x6C /* l */) && | |||
| 566 | (**cursor != 0x77 /* w */) && | |||
| 567 | (**cursor != 0x63 /* c */) && | |||
| 568 | (**cursor != 0x69 /* i */) && | |||
| 569 | (**cursor != 0x6D /* m */) && | |||
| 570 | (**cursor != 0x6E /* n */) && | |||
| 571 | (**cursor != 0x6F /* o */) && | |||
| 572 | (**cursor != 0x78 /* x */) && | |||
| 573 | (**cursor != 0x58 /* X */) && | |||
| 574 | (**cursor != 0x59 /* Y */)) { | |||
| 575 | break; | |||
| 576 | } | |||
| 577 | (*cursor)++; | |||
| 578 | } | |||
| 579 | toklen = (*cursor) - tokstart; | |||
| 580 | ||||
| 581 | if (toklen == 5) { | |||
| 582 | /* scale, skewX, skewY */ | |||
| 583 | if (strncmp("scale", tokstart, 5) == 0) { | |||
| 584 | transform = TRANSFORM_SCALE; | |||
| 585 | } else if (strncmp("skewX", tokstart, 5) == 0) { | |||
| 586 | transform = TRANSFORM_SKEWX; | |||
| 587 | } else if (strncmp("skewY", tokstart, 5) == 0) { | |||
| 588 | transform = TRANSFORM_SKEWY; | |||
| 589 | } | |||
| 590 | } else if (toklen == 6) { | |||
| 591 | /* matrix, rotate */ | |||
| 592 | if (strncmp("matrix", tokstart, 6) == 0) { | |||
| 593 | transform = TRANSFORM_MATRIX; | |||
| 594 | } else if (strncmp("rotate", tokstart, 6) == 0) { | |||
| 595 | transform = TRANSFORM_ROTATE; | |||
| 596 | } | |||
| 597 | } else if (toklen == 9) { | |||
| 598 | /* translate */ | |||
| 599 | if (strncmp("translate", tokstart, 9) == 0) { | |||
| 600 | transform = TRANSFORM_TRANSLATE; | |||
| 601 | } | |||
| 602 | } | |||
| 603 | if (transform == TRANSFORM_UNK) { | |||
| 604 | /* invalid transform */ | |||
| 605 | return svgtiny_SVG_ERROR; | |||
| 606 | } | |||
| 607 | ||||
| 608 | *transformout = transform; | |||
| 609 | return svgtiny_OK; | |||
| 610 | } | |||
| 611 | ||||
| 612 | ||||
| 613 | /** | |||
| 614 | * parse transform function parameters | |||
| 615 | * | |||
| 616 | * \param cursor current cursor | |||
| 617 | * \param textend end of buffer | |||
| 618 | * \param paramc max number of permitted parameters on input and number found on output | |||
| 619 | * \param paramv vector of float point numbers to put result in must have space for paramc entries | |||
| 620 | * \return svgtiny_OK and paramc and paramv updated or svgtiny_SVG_ERROR on error | |||
| 621 | */ | |||
| 622 | static inline svgtiny_code | |||
| 623 | parse_transform_parameters(const char **cursor, | |||
| 624 | const char *textend, | |||
| 625 | int *paramc, | |||
| 626 | float *paramv) | |||
| 627 | { | |||
| 628 | int param_idx = 0; | |||
| 629 | int param_max; | |||
| 630 | const char *tokend; | |||
| 631 | svgtiny_code err; | |||
| 632 | ||||
| 633 | param_max = *paramc; | |||
| 634 | ||||
| 635 | for(param_idx = 0; param_idx < param_max; param_idx++) { | |||
| 636 | tokend = textend; | |||
| 637 | err = svgtiny_parse_number(*cursor, &tokend, ¶mv[param_idx]); | |||
| 638 | if (err != svgtiny_OK) { | |||
| 639 | /* failed to parse number */ | |||
| 640 | return err; | |||
| 641 | } | |||
| 642 | *cursor = tokend; | |||
| 643 | ||||
| 644 | /* advance cursor past optional whitespace */ | |||
| 645 | advance_whitespace(cursor, textend); | |||
| 646 | ||||
| 647 | if (*cursor >= textend) { | |||
| 648 | /* parameter list without close parenteses */ | |||
| 649 | return svgtiny_SVG_ERROR; | |||
| 650 | } | |||
| 651 | ||||
| 652 | /* close parentheses ends parameters */ | |||
| 653 | if (**cursor == 0x29 /* ) */) { | |||
| 654 | (*cursor)++; | |||
| 655 | *paramc = param_idx + 1; | |||
| 656 | return svgtiny_OK; | |||
| 657 | } | |||
| 658 | ||||
| 659 | /* comma can be skipped */ | |||
| 660 | if (**cursor == 0x2C /* , */) { | |||
| 661 | (*cursor)++; | |||
| 662 | if ((*cursor) >= textend) { | |||
| 663 | /* parameter list without close parenteses */ | |||
| 664 | return svgtiny_SVG_ERROR; | |||
| 665 | } | |||
| 666 | } | |||
| 667 | ||||
| 668 | if ((*cursor) == tokend) { | |||
| 669 | /* no comma or whitespace between parameters */ | |||
| 670 | return svgtiny_SVG_ERROR; | |||
| 671 | } | |||
| 672 | } | |||
| 673 | /* too many parameters for transform given */ | |||
| 674 | return svgtiny_SVG_ERROR; | |||
| 675 | } | |||
| 676 | ||||
| 677 | ||||
| 678 | /** | |||
| 679 | * convert ascii hex digit to an integer | |||
| 680 | */ | |||
| 681 | static inline unsigned int hexd_to_int(const char *digit) | |||
| 682 | { | |||
| 683 | unsigned int value = *digit; | |||
| 684 | ||||
| 685 | if ((*digit >= 0x30 /* 0 */) && (*digit <= 0x39 /* 9 */)) { | |||
| 686 | value -= 0x30; | |||
| 687 | } else if ((*digit >= 0x41 /* A */) && (*digit <= 0x46 /* F */) ) { | |||
| 688 | value -= 0x37; | |||
| 689 | } else if (((*digit >= 0x61 /* a */) && (*digit <= 0x66 /* f */))) { | |||
| 690 | value -= 0x57; | |||
| 691 | } | |||
| 692 | return value; | |||
| 693 | } | |||
| 694 | ||||
| 695 | ||||
| 696 | /** | |||
| 697 | * convert two ascii hex digits to an integer | |||
| 698 | */ | |||
| 699 | static inline unsigned int hexdd_to_int(const char *digits) | |||
| 700 | { | |||
| 701 | return (hexd_to_int(digits) << 4) | hexd_to_int(digits + 1); | |||
| 702 | } | |||
| 703 | ||||
| 704 | ||||
| 705 | /** | |||
| 706 | * parse a hex colour | |||
| 707 | * https://www.w3.org/TR/css-color-4/#typedef-hex-color | |||
| 708 | */ | |||
| 709 | static inline svgtiny_code | |||
| 710 | parse_hex_color(const char **cursor, const char *textend, svgtiny_colour *c) | |||
| 711 | { | |||
| 712 | unsigned int r, g, b, a=0xff; | |||
| 713 | const char *tokstart; | |||
| 714 | ||||
| 715 | /* hex-color */ | |||
| 716 | if ((**cursor != '#') || ((textend - (*cursor)) < 4)) { | |||
| 717 | return svgtiny_SVG_ERROR; | |||
| 718 | } | |||
| 719 | ||||
| 720 | (*cursor)++; | |||
| 721 | tokstart = *cursor; | |||
| 722 | ||||
| 723 | advance_hex(cursor, textend); | |||
| 724 | ||||
| 725 | switch ((*cursor) - tokstart) { | |||
| 726 | case 3: | |||
| 727 | r = hexd_to_int(tokstart); | |||
| 728 | g = hexd_to_int(tokstart + 1); | |||
| 729 | b = hexd_to_int(tokstart + 2); | |||
| 730 | r |= r << 4; | |||
| 731 | g |= g << 4; | |||
| 732 | b |= b << 4; | |||
| 733 | break; | |||
| 734 | case 4: | |||
| 735 | r = hexd_to_int(tokstart); | |||
| 736 | g = hexd_to_int(tokstart + 1); | |||
| 737 | b = hexd_to_int(tokstart + 2); | |||
| 738 | a = hexd_to_int(tokstart + 3); | |||
| 739 | r |= r << 4; | |||
| 740 | g |= g << 4; | |||
| 741 | b |= b << 4; | |||
| 742 | break; | |||
| 743 | case 6: | |||
| 744 | r = hexdd_to_int(tokstart); | |||
| 745 | g = hexdd_to_int(tokstart + 2); | |||
| 746 | b = hexdd_to_int(tokstart + 4); | |||
| 747 | *c = svgtiny_RGB(r, g, b)((r) << 16 | (g) << 8 | (b)); | |||
| 748 | break; | |||
| 749 | case 8: | |||
| 750 | r = hexdd_to_int(tokstart); | |||
| 751 | g = hexdd_to_int(tokstart + 2); | |||
| 752 | b = hexdd_to_int(tokstart + 4); | |||
| 753 | a = hexdd_to_int(tokstart + 6); | |||
| 754 | break; | |||
| 755 | default: | |||
| 756 | /* unparsable hex color */ | |||
| 757 | *cursor = tokstart - 1; /* put cursor back */ | |||
| 758 | return svgtiny_SVG_ERROR; | |||
| 759 | } | |||
| 760 | ||||
| 761 | /** \todo do something with the alpha */ | |||
| 762 | UNUSED(a)((void) (a)); | |||
| 763 | ||||
| 764 | *c = svgtiny_RGB(r, g, b)((r) << 16 | (g) << 8 | (b)); | |||
| 765 | return svgtiny_OK; | |||
| 766 | } | |||
| 767 | ||||
| 768 | ||||
| 769 | /** | |||
| 770 | * parse a color function | |||
| 771 | * | |||
| 772 | * https://www.w3.org/TR/css-color-5/#typedef-color-function | |||
| 773 | * | |||
| 774 | * The only actual supported color function is rgb | |||
| 775 | */ | |||
| 776 | static inline svgtiny_code | |||
| 777 | parse_color_function(const char **cursorout, | |||
| 778 | const char *textend, | |||
| 779 | svgtiny_colour *c) | |||
| 780 | { | |||
| 781 | const char *cursor = *cursorout; | |||
| 782 | const char *argend = cursor; | |||
| 783 | svgtiny_code res; | |||
| 784 | float argf[4]; | |||
| 785 | int idx; /* argument index */ | |||
| 786 | ||||
| 787 | if ((textend - cursor) < 10) { | |||
| 788 | /* must be at least ten characters to be a valid function */ | |||
| 789 | return svgtiny_SVG_ERROR; | |||
| 790 | } | |||
| 791 | ||||
| 792 | if (((cursor[0] != 'r') && (cursor[0] != 'R')) || | |||
| 793 | ((cursor[1] != 'g') && (cursor[1] != 'G')) || | |||
| 794 | ((cursor[2] != 'b') && (cursor[2] != 'B')) || | |||
| 795 | (cursor[3] != '(')) | |||
| 796 | { | |||
| 797 | /* only function currently supported is rgb */ | |||
| 798 | return svgtiny_SVG_ERROR; | |||
| 799 | } | |||
| 800 | cursor += 4; | |||
| 801 | ||||
| 802 | for (idx = 0; idx < 4; idx++) { | |||
| 803 | argend = textend; | |||
| 804 | res = svgtiny_parse_number(cursor, &argend, &argf[idx]); | |||
| 805 | if (res != svgtiny_OK) { | |||
| 806 | break; | |||
| 807 | } | |||
| 808 | cursor = argend; | |||
| 809 | if (cursor >= textend) { | |||
| 810 | /* no more input */ | |||
| 811 | break; | |||
| 812 | } | |||
| 813 | if (*cursor == '%') { | |||
| 814 | /* percentage */ | |||
| 815 | argf[idx] = argf[idx] * 255 / 100; | |||
| 816 | cursor++; | |||
| 817 | } | |||
| 818 | /* value must be clamped */ | |||
| 819 | if (argf[idx] < 0) { | |||
| 820 | argf[idx] = 0; | |||
| 821 | } | |||
| 822 | if (argf[idx] > 255) { | |||
| 823 | argf[idx] = 255; | |||
| 824 | } | |||
| 825 | /* advance cursor to next argument */ | |||
| 826 | advance_whitespace(&cursor, textend); | |||
| 827 | if (cursor >= textend) { | |||
| 828 | /* no more input */ | |||
| 829 | break; | |||
| 830 | } | |||
| 831 | if (*cursor == ')') { | |||
| 832 | /* close parenthesis, arguments are complete */ | |||
| 833 | cursor++; | |||
| 834 | break; | |||
| 835 | } | |||
| 836 | if (*cursor == ',') { | |||
| 837 | /* skip optional comma */ | |||
| 838 | cursor++; | |||
| 839 | } | |||
| 840 | } | |||
| 841 | if (idx < 2 || idx > 3) { | |||
| 842 | return svgtiny_SVG_ERROR; | |||
| 843 | } | |||
| 844 | ||||
| 845 | *cursorout = cursor; | |||
| 846 | *c = svgtiny_RGB((unsigned int)argf[0],(((unsigned int)argf[0]) << 16 | ((unsigned int)argf[1] ) << 8 | ((unsigned int)argf[2])) | |||
| 847 | (unsigned int)argf[1],(((unsigned int)argf[0]) << 16 | ((unsigned int)argf[1] ) << 8 | ((unsigned int)argf[2])) | |||
| 848 | (unsigned int)argf[2])(((unsigned int)argf[0]) << 16 | ((unsigned int)argf[1] ) << 8 | ((unsigned int)argf[2])); | |||
| 849 | return svgtiny_OK; | |||
| 850 | } | |||
| 851 | ||||
| 852 | ||||
| 853 | /** | |||
| 854 | * parse a paint url | |||
| 855 | * | |||
| 856 | * /todo this does not cope with any url that is not a fragment which | |||
| 857 | * identifies a gradient paint server. | |||
| 858 | */ | |||
| 859 | static inline svgtiny_code | |||
| 860 | parse_paint_url(const char **cursorout, | |||
| 861 | const char *textend, | |||
| 862 | struct svgtiny_parse_state_gradient *grad, | |||
| 863 | struct svgtiny_parse_state *state, | |||
| 864 | svgtiny_colour *c) | |||
| 865 | { | |||
| 866 | const char *cursor = *cursorout; | |||
| 867 | svgtiny_code res; | |||
| 868 | dom_element *ref; /* referenced element */ | |||
| 869 | ||||
| 870 | if (grad == NULL((void*)0)) { | |||
| 871 | return svgtiny_SVG_ERROR; | |||
| 872 | } | |||
| 873 | ||||
| 874 | if ((textend - cursor) < 6) { | |||
| 875 | /* must be at least six characters to be a url */ | |||
| 876 | return svgtiny_SVG_ERROR; | |||
| 877 | } | |||
| 878 | ||||
| 879 | if (((cursor[0] != 'u') && (cursor[0] != 'U')) || | |||
| 880 | ((cursor[1] != 'r') && (cursor[1] != 'R')) || | |||
| 881 | ((cursor[2] != 'l') && (cursor[2] != 'L')) || | |||
| 882 | (cursor[3] != '(')) | |||
| 883 | { | |||
| 884 | /* only function currently supported is url */ | |||
| 885 | return svgtiny_SVG_ERROR; | |||
| 886 | } | |||
| 887 | cursor += 4; | |||
| 888 | ||||
| 889 | res = element_from_url(&cursor, textend - cursor, state, &ref); | |||
| 890 | if (res != svgtiny_OK){ | |||
| 891 | return res; | |||
| 892 | } | |||
| 893 | if (ref == NULL((void*)0)) { | |||
| 894 | /* unable to find referenced element */ | |||
| 895 | return svgtiny_SVG_ERROR; | |||
| 896 | } | |||
| 897 | ||||
| 898 | if ((cursor >= textend) || (*cursor != ')')) { | |||
| 899 | /* no close bracket on url */ | |||
| 900 | dom_node_unref(ref)dom_node_unref((dom_node *) (ref)); | |||
| 901 | return svgtiny_SVG_ERROR; | |||
| 902 | } | |||
| 903 | cursor++; | |||
| 904 | ||||
| 905 | /* find and update gradient */ | |||
| 906 | res = svgtiny_update_gradient(ref, state, grad); | |||
| 907 | if (res == svgtiny_OK) { | |||
| 908 | *c = svgtiny_LINEAR_GRADIENT0x2000000; | |||
| 909 | } | |||
| 910 | dom_node_unref(ref)dom_node_unref((dom_node *) (ref)); | |||
| 911 | ||||
| 912 | return res; | |||
| 913 | } | |||
| 914 | ||||
| 915 | ||||
| 916 | /** | |||
| 917 | * parse a none token | |||
| 918 | * | |||
| 919 | * \return svgtiny_OK if none found else svgtiny_SVG_ERROR if not | |||
| 920 | */ | |||
| 921 | svgtiny_code svgtiny_parse_none(const char *cursor, const char *textend) | |||
| 922 | { | |||
| 923 | const char *noneend; | |||
| 924 | if ((textend - cursor) < 4) { | |||
| 925 | /* too short to be none */ | |||
| 926 | return svgtiny_SVG_ERROR; | |||
| 927 | } | |||
| 928 | if (cursor[0] != 'n' || | |||
| 929 | cursor[1] != 'o' || | |||
| 930 | cursor[2] != 'n' || | |||
| 931 | cursor[3] != 'e') { | |||
| 932 | /* keyword doesnt match */ | |||
| 933 | return svgtiny_SVG_ERROR; | |||
| 934 | } | |||
| 935 | cursor += 4; | |||
| 936 | noneend = cursor; | |||
| 937 | ||||
| 938 | advance_whitespace(&cursor, textend); | |||
| 939 | if ((noneend != textend) && (noneend == cursor)) { | |||
| 940 | /* trailing stuff that is not whitespace */ | |||
| 941 | return svgtiny_SVG_ERROR; | |||
| 942 | } | |||
| 943 | return svgtiny_OK; | |||
| 944 | } | |||
| 945 | ||||
| 946 | /** | |||
| 947 | * Parse a paint. | |||
| 948 | * | |||
| 949 | * https://www.w3.org/TR/SVG11/painting.html#SpecifyingPaint | |||
| 950 | * https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint | |||
| 951 | * | |||
| 952 | */ | |||
| 953 | static svgtiny_code | |||
| 954 | svgtiny_parse_paint(const char *text, | |||
| 955 | size_t textlen, | |||
| 956 | struct svgtiny_parse_state_gradient *grad, | |||
| 957 | struct svgtiny_parse_state *state, | |||
| 958 | svgtiny_colour *c) | |||
| 959 | { | |||
| 960 | const char *cursor = text; /* cursor */ | |||
| 961 | const char *textend = text + textlen; | |||
| 962 | svgtiny_code res; | |||
| 963 | ||||
| 964 | advance_whitespace(&cursor, textend); | |||
| 965 | ||||
| 966 | res = svgtiny_parse_none(cursor, textend); | |||
| 967 | if (res == svgtiny_OK) { | |||
| 968 | *c = svgtiny_TRANSPARENT0x1000000; | |||
| 969 | return res; | |||
| 970 | } | |||
| 971 | ||||
| 972 | /* attempt to parse element as a paint url */ | |||
| 973 | res = parse_paint_url(&cursor, textend, grad, state, c); | |||
| 974 | if (res == svgtiny_OK) { | |||
| 975 | return res; | |||
| 976 | } | |||
| 977 | ||||
| 978 | return svgtiny_parse_color(cursor, textend - cursor, c); | |||
| 979 | } | |||
| 980 | ||||
| 981 | ||||
| 982 | /** | |||
| 983 | * parse an offset | |||
| 984 | */ | |||
| 985 | static svgtiny_code | |||
| 986 | svgtiny_parse_offset(const char *text, size_t textlen, float *offset) | |||
| 987 | { | |||
| 988 | svgtiny_code err; | |||
| 989 | float number; | |||
| 990 | const char *numend; | |||
| 991 | ||||
| 992 | numend = text + textlen; | |||
| 993 | err = svgtiny_parse_number(text, &numend, &number); | |||
| 994 | if (err != svgtiny_OK) { | |||
| 995 | return err; | |||
| 996 | } | |||
| 997 | if ((numend < (text + textlen)) && (*numend == '%')) { | |||
| 998 | number /= 100.0; | |||
| 999 | } | |||
| 1000 | /* ensure value between 0 and 1 */ | |||
| 1001 | if (number < 0) { | |||
| 1002 | number = 0; | |||
| 1003 | } | |||
| 1004 | if (number > 1.0) { | |||
| 1005 | number = 1.0; | |||
| 1006 | } | |||
| 1007 | *offset = number; | |||
| 1008 | return svgtiny_OK; | |||
| 1009 | } | |||
| 1010 | ||||
| 1011 | ||||
| 1012 | /** | |||
| 1013 | * dispatch parse operation | |||
| 1014 | */ | |||
| 1015 | static inline svgtiny_code | |||
| 1016 | dispatch_op(const char *value, | |||
| 1017 | size_t value_len, | |||
| 1018 | struct svgtiny_parse_state *state, | |||
| 1019 | struct svgtiny_parse_internal_operation *styleop) | |||
| 1020 | { | |||
| 1021 | float parse_len; | |||
| 1022 | svgtiny_code res = svgtiny_OK; | |||
| 1023 | ||||
| 1024 | switch (styleop->operation) { | |||
| 1025 | case SVGTIOP_NONE: | |||
| 1026 | res = svgtiny_SVG_ERROR; | |||
| 1027 | break; | |||
| 1028 | ||||
| 1029 | case SVGTIOP_PAINT: | |||
| 1030 | res = svgtiny_parse_paint(value, | |||
| 1031 | value_len, | |||
| 1032 | styleop->param, | |||
| 1033 | state, | |||
| 1034 | styleop->value); | |||
| 1035 | break; | |||
| 1036 | ||||
| 1037 | case SVGTIOP_COLOR: | |||
| 1038 | res = svgtiny_parse_color(value, value_len, styleop->value); | |||
| 1039 | break; | |||
| 1040 | ||||
| 1041 | case SVGTIOP_LENGTH: | |||
| 1042 | res = svgtiny_parse_length(value, | |||
| 1043 | value_len, | |||
| 1044 | *((int *)styleop->param), | |||
| 1045 | styleop->value); | |||
| 1046 | break; | |||
| 1047 | ||||
| 1048 | case SVGTIOP_INTLENGTH: | |||
| 1049 | res = svgtiny_parse_length(value, | |||
| 1050 | value_len, | |||
| 1051 | *((int *)styleop->param), | |||
| 1052 | &parse_len); | |||
| 1053 | *((int *)styleop->value) = parse_len; | |||
| 1054 | break; | |||
| 1055 | ||||
| 1056 | case SVGTIOP_OFFSET: | |||
| 1057 | res = svgtiny_parse_offset(value, value_len, styleop->value); | |||
| 1058 | break; | |||
| 1059 | } | |||
| 1060 | return res; | |||
| 1061 | } | |||
| 1062 | ||||
| 1063 | /** | |||
| 1064 | * parse a declaration in a style | |||
| 1065 | * | |||
| 1066 | * https://www.w3.org/TR/CSS21/syndata.html#declaration | |||
| 1067 | * | |||
| 1068 | * \param declaration The declaration without any preceeding space | |||
| 1069 | * \param end The end of the declaration string | |||
| 1070 | * \param state parse state to pass on | |||
| 1071 | * \param styleops The table of style operations to apply | |||
| 1072 | * | |||
| 1073 | * declaration is "<property name> : <property value>" | |||
| 1074 | */ | |||
| 1075 | static inline svgtiny_code | |||
| 1076 | parse_declaration(const char *declaration, | |||
| 1077 | const char *end, | |||
| 1078 | struct svgtiny_parse_state *state, | |||
| 1079 | struct svgtiny_parse_internal_operation *styleops) | |||
| 1080 | { | |||
| 1081 | const char *cursor = declaration; /* text cursor */ | |||
| 1082 | size_t key_len; /* key length */ | |||
| 1083 | struct svgtiny_parse_internal_operation *styleop; | |||
| 1084 | ||||
| 1085 | /* declaration must be at least 3 characters long (ie "a:b") */ | |||
| 1086 | if ((end - declaration) < 3) { | |||
| 1087 | return svgtiny_SVG_ERROR; | |||
| 1088 | } | |||
| 1089 | ||||
| 1090 | /* find end of key */ | |||
| 1091 | advance_property_name(&cursor, end); | |||
| 1092 | ||||
| 1093 | if ((cursor - declaration) < 1) { | |||
| 1094 | /* no key */ | |||
| 1095 | return svgtiny_SVG_ERROR; | |||
| 1096 | } | |||
| 1097 | ||||
| 1098 | key_len = cursor - declaration; | |||
| 1099 | ||||
| 1100 | advance_whitespace(&cursor, end); | |||
| 1101 | ||||
| 1102 | if ((cursor >= end) || (*cursor != ':')) { | |||
| 1103 | /* no colon */ | |||
| 1104 | return svgtiny_SVG_ERROR; | |||
| 1105 | } | |||
| 1106 | cursor++; /* advance over colon */ | |||
| 1107 | ||||
| 1108 | advance_whitespace(&cursor, end); | |||
| 1109 | ||||
| 1110 | /* search style operations for a match */ | |||
| 1111 | for (styleop = styleops; styleop->key != NULL((void*)0); styleop++) { | |||
| 1112 | if ((dom_string_byte_length(styleop->key) == key_len) && | |||
| 1113 | (memcmp(declaration, dom_string_data(styleop->key), key_len) == 0)) { | |||
| 1114 | /* found the operation, stop iterating */ | |||
| 1115 | return dispatch_op(cursor, end - cursor, state, styleop); | |||
| 1116 | } | |||
| 1117 | } | |||
| 1118 | ||||
| 1119 | return svgtiny_OK; | |||
| 1120 | } | |||
| 1121 | ||||
| 1122 | ||||
| 1123 | /** | |||
| 1124 | * parse text points into path points | |||
| 1125 | * | |||
| 1126 | * \param data Source text to parse | |||
| 1127 | * \param datalen Length of source text | |||
| 1128 | * \param pointv output vector of path elements. | |||
| 1129 | * \param pointc on input has number of path elements in pointv on exit has | |||
| 1130 | * the number of elements placed in the output vector. | |||
| 1131 | * \return svgtiny_OK on success else error code. | |||
| 1132 | * | |||
| 1133 | * parses a poly[line|gon] points text into a series of path elements. | |||
| 1134 | * The syntax is defined in https://www.w3.org/TR/SVG11/shapes.html#PointsBNF or | |||
| 1135 | * https://svgwg.org/svg2-draft/shapes.html#DataTypePoints | |||
| 1136 | * | |||
| 1137 | * This is a series of numbers separated by 0 (started by sign) | |||
| 1138 | * or more tabs (0x9), spaces (0x20), carrige returns (0xD) and newlines (0xA) | |||
| 1139 | * there may also be a comma in the separating whitespace after the preamble | |||
| 1140 | * A number is defined as https://www.w3.org/TR/css-syntax-3/#typedef-number-token | |||
| 1141 | * | |||
| 1142 | */ | |||
| 1143 | svgtiny_code | |||
| 1144 | svgtiny_parse_poly_points(const char *text, | |||
| 1145 | size_t textlen, | |||
| 1146 | float *pointv, | |||
| 1147 | unsigned int *pointc) | |||
| 1148 | { | |||
| 1149 | const char *textend = text + textlen; | |||
| 1150 | const char *numberend = NULL((void*)0); | |||
| 1151 | const char *cursor = text; /* text cursor */ | |||
| 1152 | int even = 0; /* is the current point even */ | |||
| 1153 | float point = 0; /* the odd point of the coordinate pair */ | |||
| 1154 | float oddpoint = 0; | |||
| 1155 | svgtiny_code err; | |||
| 1156 | ||||
| 1157 | *pointc = 0; | |||
| 1158 | ||||
| 1159 | while (cursor < textend) { | |||
| 1160 | numberend=textend; | |||
| 1161 | err = svgtiny_parse_number(cursor, &numberend, &point); | |||
| 1162 | if (err != svgtiny_OK) { | |||
| 1163 | break; | |||
| 1164 | } | |||
| 1165 | cursor = numberend; | |||
| 1166 | ||||
| 1167 | if (even) { | |||
| 1168 | even = 0; | |||
| 1169 | pointv[(*pointc)++] = svgtiny_PATH_LINE; | |||
| 1170 | pointv[(*pointc)++] = oddpoint; | |||
| 1171 | pointv[(*pointc)++] = point; | |||
| 1172 | } else { | |||
| 1173 | even = 1; | |||
| 1174 | oddpoint=point; | |||
| 1175 | } | |||
| 1176 | ||||
| 1177 | /* advance cursor past whitespace (or comma) */ | |||
| 1178 | advance_comma_whitespace(&cursor, textend); | |||
| 1179 | } | |||
| 1180 | ||||
| 1181 | return svgtiny_OK; | |||
| 1182 | } | |||
| 1183 | ||||
| 1184 | ||||
| 1185 | /** | |||
| 1186 | * Parse a length as a number of pixels. | |||
| 1187 | */ | |||
| 1188 | svgtiny_code | |||
| 1189 | svgtiny_parse_length(const char *text, | |||
| 1190 | size_t textlen, | |||
| 1191 | int viewport_size, | |||
| 1192 | float *length) | |||
| 1193 | { | |||
| 1194 | svgtiny_code err; | |||
| 1195 | float number; | |||
| 1196 | const char *unit; | |||
| 1197 | int unitlen; | |||
| 1198 | float font_size = 20; /*css_len2px(&state.style.font_size.value.length, 0);*/ | |||
| 1199 | ||||
| 1200 | unit = text + textlen; | |||
| 1201 | err = svgtiny_parse_number(text, &unit, &number); | |||
| 1202 | if (err != svgtiny_OK) { | |||
| 1203 | unitlen = -1; | |||
| 1204 | } else { | |||
| 1205 | unitlen = (text + textlen) - unit; | |||
| 1206 | } | |||
| 1207 | ||||
| 1208 | /* discount whitespace on the end of the unit */ | |||
| 1209 | while(unitlen > 0) { | |||
| 1210 | if ((unit[unitlen - 1] != 0x20) && | |||
| 1211 | (unit[unitlen - 1] != 0x09) && | |||
| 1212 | (unit[unitlen - 1] != 0x0A) && | |||
| 1213 | (unit[unitlen - 1] != 0x0D)) { | |||
| 1214 | break; | |||
| 1215 | } | |||
| 1216 | unitlen--; | |||
| 1217 | } | |||
| 1218 | ||||
| 1219 | /* decode the unit */ | |||
| 1220 | *length = 0; | |||
| 1221 | switch (unitlen) { | |||
| 1222 | case 0: | |||
| 1223 | /* no unit, assume pixels */ | |||
| 1224 | *length = number; | |||
| 1225 | break; | |||
| 1226 | case 1: | |||
| 1227 | if (unit[0] == '%') { | |||
| 1228 | /* percentage of viewport */ | |||
| 1229 | *length = number / 100.0 * viewport_size; | |||
| 1230 | } | |||
| 1231 | break; | |||
| 1232 | ||||
| 1233 | case 2: | |||
| 1234 | if (unit[0] == 'e' && unit[1] == 'm') { | |||
| 1235 | *length = number * font_size; | |||
| 1236 | } else if (unit[0] == 'e' && unit[1] == 'x') { | |||
| 1237 | *length = number / 2.0 * font_size; | |||
| 1238 | } else if (unit[0] == 'p' && unit[1] == 'x') { | |||
| 1239 | *length = number; | |||
| 1240 | } else if (unit[0] == 'p' && unit[1] == 't') { | |||
| 1241 | *length = number * 1.25; | |||
| 1242 | } else if (unit[0] == 'p' && unit[1] == 'c') { | |||
| 1243 | *length = number * 15.0; | |||
| 1244 | } else if (unit[0] == 'm' && unit[1] == 'm') { | |||
| 1245 | *length = number * 3.543307; | |||
| 1246 | } else if (unit[0] == 'c' && unit[1] == 'm') { | |||
| 1247 | *length = number * 35.43307; | |||
| 1248 | } else if (unit[0] == 'i' && unit[1] == 'n') { | |||
| 1249 | *length = number * 90; | |||
| 1250 | } | |||
| 1251 | break; | |||
| 1252 | ||||
| 1253 | default: | |||
| 1254 | /* unknown unit */ | |||
| 1255 | break; | |||
| 1256 | } | |||
| 1257 | ||||
| 1258 | return svgtiny_OK; | |||
| 1259 | } | |||
| 1260 | ||||
| 1261 | ||||
| 1262 | /** | |||
| 1263 | * Parse and apply a transform attribute. | |||
| 1264 | * | |||
| 1265 | * https://www.w3.org/TR/SVG11/coords.html#TransformAttribute | |||
| 1266 | * | |||
| 1267 | * parse transforms into transform matrix | |||
| 1268 | * | a c e | | |||
| 1269 | * | b d f | | |||
| 1270 | * | 0 0 1 | | |||
| 1271 | * | |||
| 1272 | * transforms to parse are: | |||
| 1273 | * | |||
| 1274 | * matrix(a b c d e f) | |||
| 1275 | * | a c e | | |||
| 1276 | * | b d f | | |||
| 1277 | * | 0 0 1 | | |||
| 1278 | * | |||
| 1279 | * translate(e f) | |||
| 1280 | * | 1 0 e | | |||
| 1281 | * | 0 1 f | | |||
| 1282 | * | 0 0 1 | | |||
| 1283 | * | |||
| 1284 | * translate(e) | |||
| 1285 | * | 1 0 e | | |||
| 1286 | * | 0 1 0 | | |||
| 1287 | * | 0 0 1 | | |||
| 1288 | * | |||
| 1289 | * scale(a d) | |||
| 1290 | * | a 0 0 | | |||
| 1291 | * | 0 d 0 | | |||
| 1292 | * | 0 0 1 | | |||
| 1293 | * | |||
| 1294 | * scale(a) | |||
| 1295 | * | a 0 0 | | |||
| 1296 | * | 0 1 0 | | |||
| 1297 | * | 0 0 1 | | |||
| 1298 | * | |||
| 1299 | * rotate(ang x y) | |||
| 1300 | * | cos(ang) -sin(ang) (-x * cos(ang) + y * sin(ang) + x) | | |||
| 1301 | * | sin(ang) cos(ang) (-x * sin(ang) - y * cos(ang) + y) | | |||
| 1302 | * | 0 0 1 | | |||
| 1303 | * | |||
| 1304 | * rotate(ang) | |||
| 1305 | * | cos(ang) -sin(ang) 0 | | |||
| 1306 | * | sin(ang) cos(ang) 0 | | |||
| 1307 | * | 0 0 1 | | |||
| 1308 | * | |||
| 1309 | * skewX(ang) | |||
| 1310 | * | 1 tan(ang) 0 | | |||
| 1311 | * | 0 1 0 | | |||
| 1312 | * | 0 0 1 | | |||
| 1313 | * | |||
| 1314 | * skewY(ang) | |||
| 1315 | * | 1 0 0 | | |||
| 1316 | * | tan(ang) 1 0 | | |||
| 1317 | * | 0 0 1 | | |||
| 1318 | * | |||
| 1319 | * | |||
| 1320 | */ | |||
| 1321 | svgtiny_code | |||
| 1322 | svgtiny_parse_transform(const char *text, | |||
| 1323 | size_t textlen, | |||
| 1324 | struct svgtiny_transformation_matrix *tm) | |||
| 1325 | { | |||
| 1326 | const char *cursor = text; /* text cursor */ | |||
| 1327 | const char *textend = text + textlen; | |||
| 1328 | enum transform_type transform = TRANSFORM_UNK; | |||
| 1329 | /* mapping of maimum number of parameters for each transform */ | |||
| 1330 | const int param_max[]={0,6,2,2,3,1,1}; | |||
| 1331 | const char *paramend; | |||
| 1332 | int paramc; | |||
| 1333 | float paramv[6]; | |||
| 1334 | svgtiny_code err; | |||
| 1335 | ||||
| 1336 | /* advance cursor past optional whitespace */ | |||
| 1337 | advance_whitespace(&cursor, textend); | |||
| 1338 | ||||
| 1339 | /* zero or more transform followed by whitespace or comma */ | |||
| 1340 | while (cursor < textend) { | |||
| 1341 | err = parse_transform_function(&cursor, textend, &transform); | |||
| 1342 | if (err != svgtiny_OK) { | |||
| 1343 | /* invalid transform */ | |||
| 1344 | goto transform_parse_complete; | |||
| 1345 | } | |||
| 1346 | ||||
| 1347 | /* advance cursor past optional whitespace */ | |||
| 1348 | advance_whitespace(&cursor, textend); | |||
| 1349 | ||||
| 1350 | /* open parentheses */ | |||
| 1351 | if (*cursor != 0x28 /* ( */) { | |||
| 1352 | /* invalid syntax */ | |||
| 1353 | goto transform_parse_complete; | |||
| 1354 | } | |||
| 1355 | cursor++; | |||
| 1356 | ||||
| 1357 | paramc=param_max[transform]; | |||
| 1358 | err = parse_transform_parameters(&cursor, textend, ¶mc, paramv); | |||
| 1359 | if (err != svgtiny_OK) { | |||
| 1360 | /* invalid parameters */ | |||
| 1361 | goto transform_parse_complete; | |||
| 1362 | } | |||
| 1363 | paramend = cursor; | |||
| 1364 | ||||
| 1365 | /* have transform type and at least one parameter */ | |||
| 1366 | ||||
| 1367 | /* apply transform */ | |||
| 1368 | err = apply_transform(transform, paramc, paramv, tm); | |||
| 1369 | if (err != svgtiny_OK) { | |||
| 1370 | /* transform failed */ | |||
| 1371 | goto transform_parse_complete; | |||
| 1372 | } | |||
| 1373 | ||||
| 1374 | /* advance cursor past whitespace (or comma) */ | |||
| 1375 | advance_comma_whitespace(&cursor, textend); | |||
| 1376 | if (cursor == paramend) { | |||
| 1377 | /* no comma or whitespace between transforms */ | |||
| 1378 | goto transform_parse_complete; | |||
| 1379 | } | |||
| 1380 | } | |||
| 1381 | ||||
| 1382 | transform_parse_complete: | |||
| 1383 | return svgtiny_OK; | |||
| 1384 | } | |||
| 1385 | ||||
| 1386 | ||||
| 1387 | /** | |||
| 1388 | * Parse a color. | |||
| 1389 | * | |||
| 1390 | * https://www.w3.org/TR/SVG11/types.html#DataTypeColor | |||
| 1391 | * https://www.w3.org/TR/css-color-5/#typedef-color | |||
| 1392 | * | |||
| 1393 | * <color> = <color-base> | currentColor | <system-color> | |||
| 1394 | * <color-base> = <hex-color> | <color-function> | <named-color> | transparent | |||
| 1395 | * <color-function> = <rgb()> | <rgba()> | <hsl()> | <hsla()> | <hwb()> | | |||
| 1396 | * <lab()> | <lch()> | <oklab()> | <oklch()> | <color()> | |||
| 1397 | * | |||
| 1398 | * \todo this does not cope with currentColor or transparent and supports only | |||
| 1399 | * the rgb color function. | |||
| 1400 | */ | |||
| 1401 | svgtiny_code | |||
| 1402 | svgtiny_parse_color(const char *text, size_t textlen, svgtiny_colour *c) | |||
| 1403 | { | |||
| 1404 | const struct svgtiny_named_color *named_color; | |||
| 1405 | const char *cursor = text; /* cursor */ | |||
| 1406 | const char *textend = text + textlen; | |||
| 1407 | svgtiny_code res; | |||
| 1408 | ||||
| 1409 | advance_whitespace(&cursor, textend); | |||
| 1410 | ||||
| 1411 | /* attempt to parse element as a hex color */ | |||
| 1412 | res = parse_hex_color(&cursor, textend, c); | |||
| 1413 | if (res == svgtiny_OK) { | |||
| 1414 | return res; | |||
| 1415 | } | |||
| 1416 | ||||
| 1417 | /* attempt to parse element as a color function */ | |||
| 1418 | res = parse_color_function(&cursor, textend, c); | |||
| 1419 | if (res == svgtiny_OK) { | |||
| 1420 | return res; | |||
| 1421 | } | |||
| 1422 | ||||
| 1423 | named_color = svgtiny_color_lookup(cursor, textend - cursor); | |||
| 1424 | if (named_color) { | |||
| 1425 | *c = named_color->color; | |||
| 1426 | return svgtiny_OK; | |||
| 1427 | } | |||
| 1428 | ||||
| 1429 | /* did not parse as a color */ | |||
| 1430 | *c = svgtiny_RGB(0, 0, 0)((0) << 16 | (0) << 8 | (0)); | |||
| 1431 | return svgtiny_SVG_ERROR; | |||
| 1432 | } | |||
| 1433 | ||||
| 1434 | /** | |||
| 1435 | * parse a viewbox attribute | |||
| 1436 | * | |||
| 1437 | * https://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute | |||
| 1438 | * https://www.w3.org/TR/SVG2/coords.html#ViewBoxAttribute | |||
| 1439 | * | |||
| 1440 | * <min-x>,? <min-y>,? <width>,? <height> | |||
| 1441 | */ | |||
| 1442 | svgtiny_code | |||
| 1443 | svgtiny_parse_viewbox(const char *text, | |||
| 1444 | size_t textlen, | |||
| 1445 | float viewport_width, | |||
| 1446 | float viewport_height, | |||
| 1447 | struct svgtiny_transformation_matrix *tm) | |||
| 1448 | { | |||
| 1449 | const char *cursor = text; /* text cursor */ | |||
| 1450 | const char *textend = text + textlen; | |||
| 1451 | const char *paramend; | |||
| 1452 | float paramv[4]; | |||
| 1453 | int paramidx = 0; | |||
| 1454 | svgtiny_code res; | |||
| 1455 | ||||
| 1456 | /* advance cursor past optional whitespace */ | |||
| 1457 | advance_whitespace(&cursor, textend); | |||
| 1458 | ||||
| 1459 | for (paramidx = 0; paramidx < 3; paramidx++) { | |||
| 1460 | paramend = textend; | |||
| 1461 | res = svgtiny_parse_number(cursor, ¶mend, ¶mv[paramidx]); | |||
| 1462 | if (res != svgtiny_OK) { | |||
| 1463 | /* failed to parse number */ | |||
| 1464 | return res; | |||
| 1465 | } | |||
| 1466 | cursor = paramend; | |||
| 1467 | advance_comma_whitespace(&cursor, textend); | |||
| 1468 | } | |||
| 1469 | paramend = textend; | |||
| 1470 | res = svgtiny_parse_number(cursor, ¶mend, ¶mv[paramidx]); | |||
| 1471 | if (res != svgtiny_OK) { | |||
| 1472 | /* failed to parse number */ | |||
| 1473 | return res; | |||
| 1474 | } | |||
| 1475 | cursor = paramend; | |||
| 1476 | advance_whitespace(&cursor, textend); | |||
| 1477 | ||||
| 1478 | if (cursor != textend) { | |||
| 1479 | /* syntax error */ | |||
| 1480 | return svgtiny_SVG_ERROR; | |||
| 1481 | } | |||
| 1482 | ||||
| 1483 | tm->a = (float)viewport_width / paramv[2]; | |||
| 1484 | tm->d = (float)viewport_height / paramv[3]; | |||
| 1485 | tm->e += -paramv[0] * tm->a; | |||
| 1486 | tm->f += -paramv[1] * tm->d; | |||
| 1487 | ||||
| 1488 | return svgtiny_OK; | |||
| 1489 | } | |||
| 1490 | ||||
| 1491 | ||||
| 1492 | /** | |||
| 1493 | * parse an inline style | |||
| 1494 | */ | |||
| 1495 | svgtiny_code | |||
| 1496 | svgtiny_parse_inline_style(dom_element *node, | |||
| 1497 | struct svgtiny_parse_state *state, | |||
| 1498 | struct svgtiny_parse_internal_operation *ops) | |||
| 1499 | { | |||
| 1500 | const char *cursor; /* text cursor */ | |||
| 1501 | const char *textend; | |||
| 1502 | const char *declaration_start; | |||
| 1503 | dom_string *attr; | |||
| 1504 | dom_exception exc; | |||
| 1505 | ||||
| 1506 | /* style attribute */ | |||
| 1507 | exc = dom_element_get_attribute(node, state->interned_style, &attr)dom_element_get_attribute( (dom_element *) (node), (state-> interned_style), (&attr)); | |||
| 1508 | if (exc != DOM_NO_ERR) { | |||
| 1509 | return svgtiny_LIBDOM_ERROR; | |||
| 1510 | } | |||
| 1511 | if (attr == NULL((void*)0)) { | |||
| 1512 | /* no style attribute */ | |||
| 1513 | return svgtiny_OK; | |||
| 1514 | } | |||
| 1515 | cursor = dom_string_data(attr); | |||
| 1516 | textend = cursor + dom_string_byte_length(attr); | |||
| 1517 | ||||
| 1518 | while (cursor < textend) { | |||
| 1519 | advance_whitespace(&cursor, textend); | |||
| 1520 | declaration_start = cursor; | |||
| 1521 | while (cursor < textend) { | |||
| 1522 | if ((*cursor == ';') && | |||
| 1523 | (*(cursor - 1) != '\\')) { | |||
| 1524 | break; | |||
| 1525 | } | |||
| 1526 | cursor++; | |||
| 1527 | } | |||
| 1528 | parse_declaration(declaration_start, cursor, state, ops); | |||
| 1529 | cursor++; /* skip semicolon */ | |||
| 1530 | } | |||
| 1531 | dom_string_unref(attr); | |||
| 1532 | return svgtiny_OK; | |||
| 1533 | } | |||
| 1534 | ||||
| 1535 | ||||
| 1536 | /** | |||
| 1537 | * parse attributes controled by operation table | |||
| 1538 | */ | |||
| 1539 | svgtiny_code | |||
| 1540 | svgtiny_parse_attributes(dom_element *node, | |||
| 1541 | struct svgtiny_parse_state *state, | |||
| 1542 | struct svgtiny_parse_internal_operation *styleops) | |||
| 1543 | { | |||
| 1544 | struct svgtiny_parse_internal_operation *styleop; | |||
| 1545 | dom_string *attr; | |||
| 1546 | dom_exception exc; | |||
| 1547 | ||||
| 1548 | for (styleop = styleops; styleop->key != NULL((void*)0); styleop++) { | |||
| 1549 | exc = dom_element_get_attribute(node, styleop->key, &attr)dom_element_get_attribute( (dom_element *) (node), (styleop-> key), (&attr)); | |||
| 1550 | if (exc != DOM_NO_ERR) { | |||
| 1551 | return svgtiny_LIBDOM_ERROR; | |||
| 1552 | } | |||
| 1553 | if (attr != NULL((void*)0)) { | |||
| 1554 | dispatch_op(dom_string_data(attr), | |||
| 1555 | dom_string_byte_length(attr), | |||
| 1556 | state, | |||
| 1557 | styleop); | |||
| 1558 | dom_string_unref(attr); | |||
| 1559 | } | |||
| 1560 | } | |||
| 1561 | return svgtiny_OK; | |||
| 1562 | } | |||
| 1563 | ||||
| 1564 | ||||
| 1565 | /** | |||
| 1566 | * parses href attribute contents as a url fragment and finds matching element | |||
| 1567 | * | |||
| 1568 | * \param node The node on which to examine teh href attribute for a url | |||
| 1569 | * \param state The parse state | |||
| 1570 | * \param element result element pointer or NULL if no matching element | |||
| 1571 | * \return svgtiny_OK and element updated else error code | |||
| 1572 | */ | |||
| 1573 | svgtiny_code | |||
| 1574 | svgtiny_parse_element_from_href(dom_element *node, | |||
| 1575 | struct svgtiny_parse_state *state, | |||
| 1576 | dom_element **element) | |||
| 1577 | { | |||
| 1578 | dom_exception exc; | |||
| 1579 | dom_string *attr; | |||
| 1580 | svgtiny_code res; | |||
| 1581 | const char *url; | |||
| 1582 | ||||
| 1583 | /* attempt to get href in default namespace */ | |||
| 1584 | exc = dom_element_get_attribute(node, state->interned_href, &attr)dom_element_get_attribute( (dom_element *) (node), (state-> interned_href), (&attr)); | |||
| 1585 | if (exc != DOM_NO_ERR) { | |||
| 1586 | return svgtiny_LIBDOM_ERROR; | |||
| 1587 | } | |||
| 1588 | ||||
| 1589 | if (attr == NULL((void*)0)) { | |||
| 1590 | dom_string *xmlns_xlink; | |||
| 1591 | exc = dom_string_create_interned((const uint8_t *)XLINK_NS"http://www.w3.org/1999/xlink", | |||
| 1592 | strlen(XLINK_NS"http://www.w3.org/1999/xlink"), | |||
| 1593 | &xmlns_xlink); | |||
| 1594 | if (exc != DOM_NO_ERR || xmlns_xlink == NULL((void*)0)) { | |||
| 1595 | return svgtiny_LIBDOM_ERROR; | |||
| 1596 | } | |||
| 1597 | ||||
| 1598 | /* attempt to get href attribute in xlink namespace */ | |||
| 1599 | exc = dom_element_get_attribute_ns(node,dom_element_get_attribute_ns((dom_element *) (node), (xmlns_xlink ), (state->interned_href), (&attr)) | |||
| 1600 | xmlns_xlink,dom_element_get_attribute_ns((dom_element *) (node), (xmlns_xlink ), (state->interned_href), (&attr)) | |||
| 1601 | state->interned_href,dom_element_get_attribute_ns((dom_element *) (node), (xmlns_xlink ), (state->interned_href), (&attr)) | |||
| 1602 | &attr)dom_element_get_attribute_ns((dom_element *) (node), (xmlns_xlink ), (state->interned_href), (&attr)); | |||
| 1603 | dom_string_unref(xmlns_xlink); | |||
| 1604 | if (exc != DOM_NO_ERR) { | |||
| 1605 | return svgtiny_LIBDOM_ERROR; | |||
| 1606 | } | |||
| 1607 | if (attr == NULL((void*)0)) { | |||
| 1608 | /* no href attribute */ | |||
| 1609 | *element = NULL((void*)0); | |||
| 1610 | return svgtiny_OK; | |||
| 1611 | } | |||
| 1612 | } | |||
| 1613 | ||||
| 1614 | url = dom_string_data(attr); | |||
| 1615 | res = element_from_url(&url, | |||
| 1616 | dom_string_byte_length(attr), | |||
| 1617 | state, | |||
| 1618 | element); | |||
| 1619 | ||||
| 1620 | dom_string_unref(attr); | |||
| 1621 | return res; | |||
| 1622 | } |