Line data Source code
1 : /* File: draw_stereotype_icon.c; Copyright and License: see below */
2 :
3 : #include "draw_svg_path_data.h"
4 : #include "draw/draw_stereotype_icon.h"
5 : #include "layout/layout_visible_set.h"
6 : #include "u8/u8_trace.h"
7 : #include "utf8stringbuf/utf8stringbuf.h"
8 : #include <stdio.h>
9 : #include <stdlib.h>
10 : #include <assert.h>
11 :
12 : const double DRAW_STEREOTYPE_ICON_WIDTH_TO_HEIGHT = 1.0;
13 :
14 0 : u8_error_t draw_stereotype_icon_draw ( const draw_stereotype_icon_t *this_,
15 : const char *stereotype,
16 : const data_profile_part_t *profile,
17 : const GdkRGBA *default_color,
18 : u8_error_info_t *out_err_info,
19 : const geometry_rectangle_t *bounds,
20 : cairo_t *cr )
21 : {
22 0 : U8_TRACE_BEGIN();
23 0 : assert( NULL != stereotype );
24 0 : assert( NULL != profile );
25 0 : assert( NULL != out_err_info );
26 0 : assert( NULL != bounds );
27 0 : assert( NULL != cr );
28 0 : u8_error_t result = U8_ERROR_NONE;
29 0 : u8_error_info_init_void( out_err_info );
30 :
31 0 : const utf8stringview_t stereotype_view = UTF8STRINGVIEW_STR(stereotype);
32 : const data_classifier_t *const optional_stereotype
33 0 : = data_profile_part_get_stereotype_by_name_const( profile, &stereotype_view );
34 0 : if ( optional_stereotype != NULL )
35 : {
36 0 : U8_TRACE_INFO_STR( "stereotype", stereotype );
37 0 : const char *const drawing_directives = data_classifier_get_description_const( optional_stereotype );
38 : geometry_rectangle_t io_view_rect;
39 0 : geometry_rectangle_init_empty( &io_view_rect );
40 0 : result |= draw_stereotype_icon_private_parse_svg_xml( this_,
41 : false, /* draw */
42 : drawing_directives,
43 : &io_view_rect,
44 : default_color,
45 : out_err_info,
46 : bounds,
47 : cr
48 : );
49 0 : if ( result == U8_ERROR_NONE )
50 : {
51 : /* make view rect quadratic */
52 0 : const double view_width = geometry_rectangle_get_width( &io_view_rect );
53 0 : const double view_height = geometry_rectangle_get_height( &io_view_rect );
54 0 : if ( view_width > view_height)
55 : {
56 0 : geometry_rectangle_set_top( &io_view_rect, geometry_rectangle_get_top( &io_view_rect ) - 0.5*(view_width-view_height) );
57 0 : geometry_rectangle_set_height( &io_view_rect, view_width );
58 : }
59 : else
60 : {
61 0 : geometry_rectangle_set_left( &io_view_rect, geometry_rectangle_get_left( &io_view_rect ) - 0.5*(view_height-view_width) );
62 0 : geometry_rectangle_set_width( &io_view_rect, view_height );
63 : }
64 0 : result |= draw_stereotype_icon_private_parse_svg_xml( this_,
65 : true, /* draw */
66 : drawing_directives,
67 : &io_view_rect,
68 : default_color,
69 : out_err_info,
70 : bounds,
71 : cr
72 : );
73 : }
74 :
75 0 : geometry_rectangle_destroy( &io_view_rect );
76 : }
77 : else
78 : {
79 0 : result = U8_ERROR_NOT_FOUND;
80 : }
81 :
82 0 : U8_TRACE_END_ERR(result);
83 0 : return result;
84 : }
85 :
86 : /*! \brief states of parsing svg, the xml parts */
87 : enum draw_stereotype_icon_xml_enum {
88 : DRAW_STEREOTYPE_ICON_XML_OUTSIDE_PATH, /*!< nothing passed yet */
89 : DRAW_STEREOTYPE_ICON_XML_TAG_STARTED, /*!< XML tag start passed */
90 : DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG, /*!< XML token path passed */
91 : DRAW_STEREOTYPE_ICON_XML_D_ATTR, /*!< XML attribute d: name passed */
92 : DRAW_STEREOTYPE_ICON_XML_D_DEF, /*!< XML attribute d: assignment passed */
93 : DRAW_STEREOTYPE_ICON_XML_FILL_ATTR, /*!< XML attribute fill: name passed, see svg spec 13.2. Specifying paint */
94 : DRAW_STEREOTYPE_ICON_XML_FILL_DEF, /*!< XML attribute fill: assignment passed */
95 : DRAW_STEREOTYPE_ICON_XML_FILL_VALUE, /*!< XML attribute fill: single or double quotes passed */
96 : DRAW_STEREOTYPE_ICON_XML_STROKE_ATTR, /*!< XML attribute stroke: name passed, see svg spec 13.2. Specifying paint */
97 : DRAW_STEREOTYPE_ICON_XML_STROKE_DEF, /*!< XML attribute stroke: assignment passed */
98 : DRAW_STEREOTYPE_ICON_XML_STROKE_VALUE, /*!< XML attribute stroke: single or double quotes passed */
99 : DRAW_STEREOTYPE_ICON_XML_INSIDE_SGLQ_VALUE, /*!< single-quoted XML attribute-value of any other attribute-name */
100 : DRAW_STEREOTYPE_ICON_XML_INSIDE_DBLQ_VALUE, /*!< double-quoted XML attribute-value of any other attribute-name */
101 : };
102 :
103 29 : u8_error_t draw_stereotype_icon_private_parse_svg_xml ( const draw_stereotype_icon_t *this_,
104 : bool draw,
105 : const char *drawing_directives,
106 : geometry_rectangle_t *io_view_rect,
107 : const GdkRGBA *default_color,
108 : u8_error_info_t *out_err_info,
109 : const geometry_rectangle_t *target_bounds,
110 : cairo_t *cr )
111 : {
112 29 : U8_TRACE_BEGIN();
113 29 : assert( NULL != drawing_directives );
114 29 : assert( NULL != io_view_rect );
115 29 : assert( NULL != out_err_info );
116 29 : assert( NULL != target_bounds );
117 29 : assert( ( ! draw ) || ( NULL != cr ) );
118 29 : u8_error_t result = U8_ERROR_NONE;
119 :
120 : /* states while parsing: */
121 29 : enum draw_stereotype_icon_xml_enum parser_state = DRAW_STEREOTYPE_ICON_XML_OUTSIDE_PATH;
122 29 : uint_fast16_t path_count = 0;
123 29 : GdkRGBA stroke_color = *default_color;
124 29 : GdkRGBA fill_color = { .red = 1.0, .green = 1.0, .blue = 1.0, .alpha = 0.0 };
125 : char xml_attr_value_buf[64]; /* max 4 floating point numbers and rgba(%,%,%,%) string around */
126 29 : utf8stringbuf_t xml_attr_value = UTF8STRINGBUF(xml_attr_value_buf);
127 :
128 29 : const utf8stringview_t drawing_directives_view = UTF8STRINGVIEW_STR( drawing_directives );
129 : utf8stringviewtokenizer_t tok_iterator;
130 29 : utf8stringviewtokenizer_init( &tok_iterator, &drawing_directives_view, UTF8STRINGVIEWTOKENMODE_TEXT );
131 214 : while( utf8stringviewtokenizer_has_next( &tok_iterator ) && ( result == U8_ERROR_NONE ) )
132 : {
133 185 : const utf8stringview_t tok = utf8stringviewtokenizer_next( &tok_iterator );
134 185 : assert( utf8stringview_get_length( &tok ) > 0 ); /* otherwise this would not be a token */
135 : /* U8_TRACE_INFO_VIEW( "token:", tok ); */
136 :
137 185 : switch ( parser_state )
138 : {
139 31 : case DRAW_STEREOTYPE_ICON_XML_OUTSIDE_PATH:
140 : {
141 31 : if ( utf8stringview_equals_str( &tok, "<" ) )
142 : {
143 28 : parser_state = DRAW_STEREOTYPE_ICON_XML_TAG_STARTED;
144 : }
145 : }
146 31 : break;
147 :
148 28 : case DRAW_STEREOTYPE_ICON_XML_TAG_STARTED:
149 : {
150 28 : if ( utf8stringview_equals_str( &tok, "path" ) )
151 : {
152 : /* for each new path, reset the colors to defaults */
153 28 : stroke_color = *default_color;
154 28 : fill_color = (GdkRGBA) { .red = 1.0, .green = 1.0, .blue = 1.0, .alpha = 0.0 };
155 28 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG;
156 : }
157 : else
158 : {
159 : /* no error, accept anythig here: */
160 : /* not a path tag, back to ouside state */
161 : /* TODO accept a namespace for the path maybe? */
162 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_OUTSIDE_PATH;
163 : }
164 : }
165 28 : break;
166 :
167 70 : case DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG:
168 : {
169 70 : if ( utf8stringview_equals_str( &tok, "d" ) )
170 : {
171 28 : parser_state = DRAW_STEREOTYPE_ICON_XML_D_ATTR;
172 : }
173 42 : else if ( utf8stringview_equals_str( &tok, "stroke" ) )
174 : {
175 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_STROKE_ATTR;
176 : }
177 42 : else if ( utf8stringview_equals_str( &tok, "fill" ) )
178 : {
179 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_FILL_ATTR;
180 : }
181 42 : else if ( utf8stringview_equals_str( &tok, "\'" ) )
182 : {
183 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_SGLQ_VALUE;
184 : }
185 42 : else if ( utf8stringview_equals_str( &tok, "\"" ) )
186 : {
187 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_DBLQ_VALUE;
188 : }
189 42 : else if ( utf8stringview_equals_str( &tok, "/" ) )
190 : {
191 : /* ignore */
192 : }
193 21 : else if ( utf8stringview_equals_str( &tok, ">" ) )
194 : {
195 21 : if ( draw )
196 : {
197 0 : assert( NULL != cr );
198 : /* fill and stroke */
199 0 : if ( fill_color.alpha > 0.01 )
200 : {
201 0 : if ( stroke_color.alpha > 0.01 )
202 : {
203 0 : cairo_set_source_rgba( cr, fill_color.red, fill_color.green, fill_color.blue, fill_color.alpha );
204 0 : cairo_fill_preserve( cr );
205 0 : cairo_set_source_rgba( cr, stroke_color.red, stroke_color.green, stroke_color.blue, stroke_color.alpha );
206 0 : cairo_stroke( cr );
207 : }
208 : else
209 : {
210 0 : cairo_set_source_rgba( cr, fill_color.red, fill_color.green, fill_color.blue, fill_color.alpha );
211 0 : cairo_fill( cr );
212 : }
213 : }
214 : else
215 : {
216 0 : cairo_set_source_rgba( cr, stroke_color.red, stroke_color.green, stroke_color.blue, stroke_color.alpha );
217 0 : cairo_stroke( cr );
218 : }
219 : }
220 : /* end of path tag */
221 21 : parser_state = DRAW_STEREOTYPE_ICON_XML_OUTSIDE_PATH;
222 : }
223 : }
224 70 : break;
225 :
226 28 : case DRAW_STEREOTYPE_ICON_XML_D_ATTR:
227 : {
228 28 : if ( utf8stringview_equals_str( &tok, "=" ) )
229 : {
230 28 : parser_state = DRAW_STEREOTYPE_ICON_XML_D_DEF;
231 : }
232 : else
233 : {
234 : /* this is an error */
235 : /* TODO the tokenizer may have split a token, e.g. d:pre-d-post */
236 : /* A possible fix is to implement a token mode for XML-NM-Tokens */
237 0 : result |= U8_ERROR_PARSER_STRUCTURE;
238 0 : u8_error_info_init_line( out_err_info,
239 : U8_ERROR_PARSER_STRUCTURE,
240 0 : utf8stringviewtokenizer_get_line( &tok_iterator )
241 : );
242 :
243 : /* not a d attribute, back to inside path state */
244 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG;
245 : }
246 : }
247 28 : break;
248 :
249 28 : case DRAW_STEREOTYPE_ICON_XML_D_DEF:
250 : {
251 28 : if (( utf8stringview_equals_str( &tok, "\"" ) )||( utf8stringview_equals_str( &tok, "\'" ) ))
252 28 : {
253 : /* process draw commands in sub statemachine */
254 28 : utf8stringviewtokenizer_set_mode( &tok_iterator, UTF8STRINGVIEWTOKENMODE_FLOAT_ONLY );
255 : draw_svg_path_data_t svg_path_data;
256 28 : draw_svg_path_data_init( &svg_path_data );
257 28 : if ( draw )
258 : {
259 0 : result |= draw_svg_path_data_draw( &svg_path_data,
260 : &tok_iterator,
261 : io_view_rect,
262 : out_err_info,
263 : target_bounds,
264 : cr
265 : );
266 : }
267 : else
268 : {
269 : geometry_rectangle_t path_view_rect;
270 28 : geometry_rectangle_init_empty( &path_view_rect );
271 28 : result |= draw_svg_path_data_parse_bounds( &svg_path_data,
272 : &tok_iterator,
273 : &path_view_rect,
274 : out_err_info
275 : );
276 28 : if ( path_count == 0 )
277 : {
278 28 : geometry_rectangle_replace( io_view_rect, &path_view_rect );
279 : }
280 : else
281 : {
282 0 : geometry_rectangle_init_by_bounds( io_view_rect, io_view_rect, &path_view_rect );
283 : }
284 28 : geometry_rectangle_destroy( &path_view_rect );
285 : }
286 28 : draw_svg_path_data_destroy( &svg_path_data );
287 28 : utf8stringviewtokenizer_set_mode( &tok_iterator, UTF8STRINGVIEWTOKENMODE_TEXT );
288 28 : path_count ++;
289 : }
290 : else
291 : {
292 : /* this is an error */
293 0 : result |= U8_ERROR_PARSER_STRUCTURE;
294 0 : u8_error_info_init_line( out_err_info,
295 : U8_ERROR_PARSER_STRUCTURE,
296 0 : utf8stringviewtokenizer_get_line( &tok_iterator )
297 : );
298 : }
299 : /* back to inside path state */
300 28 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG;
301 : }
302 28 : break;
303 :
304 0 : case DRAW_STEREOTYPE_ICON_XML_STROKE_ATTR:
305 : {
306 0 : if ( utf8stringview_equals_str( &tok, "=" ) )
307 : {
308 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_STROKE_DEF;
309 : }
310 : else
311 : {
312 : /* this is an error */
313 : /* TODO the tokenizer may have split a token, e.g. stroke:pre-stroke-post */
314 : /* A possible fix is to implement a token mode for XML-NM-Tokens */
315 0 : result |= U8_ERROR_PARSER_STRUCTURE;
316 0 : u8_error_info_init_line( out_err_info,
317 : U8_ERROR_PARSER_STRUCTURE,
318 0 : utf8stringviewtokenizer_get_line( &tok_iterator )
319 : );
320 :
321 : /* not a stroke attribute, back to inside path state */
322 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG;
323 : }
324 : }
325 0 : break;
326 :
327 0 : case DRAW_STEREOTYPE_ICON_XML_STROKE_DEF:
328 : {
329 0 : if (( utf8stringview_equals_str( &tok, "\"" ) )||( utf8stringview_equals_str( &tok, "\'" ) ))
330 : {
331 : /* end of the value of another, ignored tag */
332 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_STROKE_VALUE;
333 0 : utf8stringbuf_clear( &xml_attr_value );
334 : }
335 : else
336 : {
337 : /* this is an error */
338 0 : result |= U8_ERROR_PARSER_STRUCTURE;
339 0 : u8_error_info_init_line( out_err_info,
340 : U8_ERROR_PARSER_STRUCTURE,
341 0 : utf8stringviewtokenizer_get_line( &tok_iterator )
342 : );
343 :
344 : /* not a stroke attribute, back to inside path state */
345 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG;
346 : }
347 : }
348 0 : break;
349 :
350 0 : case DRAW_STEREOTYPE_ICON_XML_STROKE_VALUE:
351 : {
352 0 : if (( utf8stringview_equals_str( &tok, "\"" ) )||( utf8stringview_equals_str( &tok, "\'" ) ))
353 : {
354 : /* parse the color */
355 0 : if ( utf8stringbuf_equals_str( &xml_attr_value, "none" ) )
356 : {
357 0 : stroke_color = (GdkRGBA) { .red = 1.0, .green = 1.0, .blue = 1.0, .alpha = 0.0 };
358 : }
359 : else
360 : {
361 0 : U8_TRACE_INFO_STR( "stroke:", utf8stringbuf_get_string( &xml_attr_value ) );
362 0 : const gboolean success = gdk_rgba_parse( &stroke_color, utf8stringbuf_get_string( &xml_attr_value ) );
363 0 : if ( ! success )
364 : {
365 0 : result |= U8_ERROR_PARSER_STRUCTURE;
366 0 : u8_error_info_init_line( out_err_info,
367 : U8_ERROR_PARSER_STRUCTURE,
368 0 : utf8stringviewtokenizer_get_line( &tok_iterator )
369 : );
370 : }
371 : }
372 : /* end of the value of fill tag */
373 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG;
374 : }
375 : else
376 : {
377 0 : utf8stringbuf_append_view( &xml_attr_value, &tok );
378 : }
379 : }
380 0 : break;
381 :
382 0 : case DRAW_STEREOTYPE_ICON_XML_FILL_ATTR:
383 : {
384 0 : if ( utf8stringview_equals_str( &tok, "=" ) )
385 : {
386 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_FILL_DEF;
387 : }
388 : else
389 : {
390 : /* this is an error */
391 : /* TODO the tokenizer may have split a token, e.g. fill:pre-fill-post */
392 : /* A possible fix is to implement a token mode for XML-NM-Tokens */
393 0 : result |= U8_ERROR_PARSER_STRUCTURE;
394 0 : u8_error_info_init_line( out_err_info,
395 : U8_ERROR_PARSER_STRUCTURE,
396 0 : utf8stringviewtokenizer_get_line( &tok_iterator )
397 : );
398 :
399 : /* not a fill attribute, back to inside path state */
400 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG;
401 : }
402 : }
403 0 : break;
404 :
405 0 : case DRAW_STEREOTYPE_ICON_XML_FILL_DEF:
406 : {
407 0 : if (( utf8stringview_equals_str( &tok, "\"" ) )||( utf8stringview_equals_str( &tok, "\'" ) ))
408 : {
409 : /* end of the value of another, ignored tag */
410 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_FILL_VALUE;
411 0 : utf8stringbuf_clear( &xml_attr_value );
412 : }
413 : else
414 : {
415 : /* this is an error */
416 0 : result |= U8_ERROR_PARSER_STRUCTURE;
417 0 : u8_error_info_init_line( out_err_info,
418 : U8_ERROR_PARSER_STRUCTURE,
419 0 : utf8stringviewtokenizer_get_line( &tok_iterator )
420 : );
421 :
422 : /* not a stroke attribute, back to inside path state */
423 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG;
424 : }
425 : }
426 0 : break;
427 :
428 0 : case DRAW_STEREOTYPE_ICON_XML_FILL_VALUE:
429 : {
430 0 : if (( utf8stringview_equals_str( &tok, "\"" ) )||( utf8stringview_equals_str( &tok, "\'" ) ))
431 : {
432 : /* parse the color */
433 0 : if ( utf8stringbuf_equals_str( &xml_attr_value, "none" ) )
434 : {
435 0 : fill_color = (GdkRGBA) { .red = 1.0, .green = 1.0, .blue = 1.0, .alpha = 0.0 };
436 : }
437 : else
438 : {
439 0 : U8_TRACE_INFO_STR( "fill:", utf8stringbuf_get_string( &xml_attr_value ) );
440 0 : const gboolean success = gdk_rgba_parse( &fill_color, utf8stringbuf_get_string( &xml_attr_value ) );
441 0 : if ( ! success )
442 : {
443 0 : result |= U8_ERROR_PARSER_STRUCTURE;
444 0 : u8_error_info_init_line( out_err_info,
445 : U8_ERROR_PARSER_STRUCTURE,
446 0 : utf8stringviewtokenizer_get_line( &tok_iterator )
447 : );
448 : }
449 : }
450 : /* end of the value of fill tag */
451 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG;
452 : }
453 : else
454 : {
455 0 : utf8stringbuf_append_view( &xml_attr_value, &tok );
456 : }
457 : }
458 0 : break;
459 :
460 0 : case DRAW_STEREOTYPE_ICON_XML_INSIDE_SGLQ_VALUE:
461 : {
462 0 : if ( utf8stringview_equals_str( &tok, "\'" ) )
463 : {
464 : /* end of the value of another, ignored tag */
465 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG;
466 : }
467 : /*
468 : else
469 : {
470 : U8_TRACE_INFO_VIEW( "other single-quoted:", tok );
471 : }
472 : */
473 : }
474 0 : break;
475 :
476 0 : case DRAW_STEREOTYPE_ICON_XML_INSIDE_DBLQ_VALUE:
477 : {
478 0 : if ( utf8stringview_equals_str( &tok, "\"" ) )
479 : {
480 : /* end of the value of another, ignored tag */
481 0 : parser_state = DRAW_STEREOTYPE_ICON_XML_INSIDE_PATH_TAG;
482 : }
483 : /*
484 : else
485 : {
486 : U8_TRACE_INFO_VIEW( "other double-quoted:", tok );
487 : }
488 : */
489 : }
490 0 : break;
491 : }
492 : }
493 :
494 : /* report error on unfinished drawing */
495 29 : if (( result == U8_ERROR_NONE )&&( parser_state != DRAW_STEREOTYPE_ICON_XML_OUTSIDE_PATH ))
496 : {
497 0 : result |= U8_ERROR_PARSER_STRUCTURE;
498 : /* if no other error encountered yet, report this one: */
499 0 : if ( ! u8_error_info_is_error( out_err_info ) )
500 : {
501 0 : u8_error_info_init_line( out_err_info,
502 : U8_ERROR_PARSER_STRUCTURE,
503 0 : utf8stringviewtokenizer_get_line( &tok_iterator )
504 : );
505 : }
506 : }
507 :
508 : /* check if anything was drawn at all */
509 29 : if ( path_count == 0 )
510 : {
511 1 : result |= U8_ERROR_NOT_FOUND;
512 : }
513 :
514 29 : utf8stringviewtokenizer_destroy( &tok_iterator );
515 29 : U8_TRACE_END_ERR(result);
516 29 : return result;
517 : }
518 :
519 :
520 : /*
521 : Copyright 2023-2025 Andreas Warnke
522 : http://www.apache.org/licenses/LICENSE-2.0
523 :
524 : Licensed under the Apache License, Version 2.0 (the "License");
525 : you may not use this file except in compliance with the License.
526 : You may obtain a copy of the License at
527 :
528 :
529 : Unless required by applicable law or agreed to in writing, software
530 : distributed under the License is distributed on an "AS IS" BASIS,
531 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
532 : See the License for the specific language governing permissions and
533 : limitations under the License.
534 : */
|