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; |
Value stored to 'idend' during its initialization is never read | |
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 | } |