Line data Source code
1 : /* File: draw_classifier_label.c; Copyright and License: see below */
2 :
3 : #include "draw/draw_classifier_label.h"
4 : #include "u8/u8_trace.h"
5 : #include "entity/data_classifier.h"
6 : #include "entity/data_diagramelement.h"
7 : #include "u8/u8_i32.h"
8 : #include "utf8stream/utf8stream_writer.h"
9 : #include "utf8stringbuf/utf8stringbuf.h"
10 : #include "utf8stringbuf/utf8string.h"
11 : #include "u8stream/universal_memory_output_stream.h"
12 : #include "u8stream/universal_output_stream.h"
13 : #include <pango/pangocairo.h>
14 : #include <stdio.h>
15 : #include <stdlib.h>
16 : #include <assert.h>
17 :
18 : static const int DRAW_CLASSIFIER_PANGO_UNLIMITED_WIDTH = -1;
19 : static const int DRAW_CLASSIFIER_PANGO_AUTO_DETECT_LENGTH = -1;
20 : #define DRAW_CLASSIFIER_LEFT_GUILLEMETS "\xc2\xab"
21 : #define DRAW_CLASSIFIER_RIGHT_GUILLEMETS "\xc2\xbb"
22 : #define DRAW_CLASSIFIER_COLON ':'
23 :
24 3 : void draw_classifier_label_init( draw_classifier_label_t *this_ )
25 : {
26 : assert( DATA_CLASSIFIER_MAX_NAME_SIZE == DATA_CLASSIFIER_MAX_STEREOTYPE_SIZE );
27 3 : utf8stream_writemem_init( &((*this_).text_builder), &((*this_).text_buffer), sizeof( (*this_).text_buffer) );
28 3 : draw_line_breaker_init( &((*this_).linebr) );
29 3 : }
30 :
31 3 : void draw_classifier_label_destroy( draw_classifier_label_t *this_ )
32 : {
33 3 : draw_line_breaker_destroy( &((*this_).linebr) );
34 3 : const u8_error_t text_err = utf8stream_writemem_destroy( &((*this_).text_builder) );
35 3 : if ( text_err != U8_ERROR_NONE )
36 : {
37 0 : U8_LOG_WARNING_HEX( "error at draw/draw_classifier_label: buffer too small", text_err );
38 : }
39 3 : }
40 :
41 256 : void draw_classifier_label_get_stereotype_and_name_dimensions( draw_classifier_label_t *this_,
42 : const data_visible_classifier_t *visible_classifier,
43 : bool with_stereotype,
44 : const geometry_dimensions_t *proposed_bounds,
45 : unsigned int max_recursions,
46 : const pencil_size_t *pencil_size,
47 : PangoLayout *font_layout,
48 : geometry_dimensions_t *out_label_dim )
49 : {
50 256 : U8_TRACE_BEGIN();
51 256 : assert( NULL != visible_classifier );
52 256 : assert( NULL != proposed_bounds );
53 256 : assert( NULL != pencil_size );
54 256 : assert( NULL != font_layout );
55 256 : assert( NULL != out_label_dim );
56 :
57 256 : if ( data_visible_classifier_is_valid( visible_classifier ) )
58 : {
59 : /* get data objects */
60 : const data_classifier_t *const classifier
61 256 : = data_visible_classifier_get_classifier_const( visible_classifier );
62 : const data_diagramelement_t *const diagramelement
63 256 : = data_visible_classifier_get_diagramelement_const( visible_classifier );
64 : const data_diagramelement_flag_t display_flags
65 256 : = data_diagramelement_get_display_flags( diagramelement );
66 :
67 : /* get layout sizes */
68 256 : const int proposed_pango_width = (int) geometry_dimensions_get_width( proposed_bounds );
69 :
70 : /* stereotype text */
71 256 : int text1_height = 0;
72 256 : int text1_width = 0;
73 : {
74 256 : if ( with_stereotype && data_classifier_has_stereotype( classifier ) )
75 : {
76 : /* prepare text */
77 256 : u8_error_t name_err = U8_ERROR_NONE;
78 256 : utf8stream_writer_t *to_name = utf8stream_writemem_get_writer( &((*this_).text_builder) );
79 256 : name_err |= utf8stream_writer_write_str( to_name, DRAW_CLASSIFIER_LEFT_GUILLEMETS );
80 256 : utf8stringview_t stereo_name = UTF8STRINGVIEW_STR( data_classifier_get_stereotype_const( classifier ) );
81 : /* insert linebreaks */
82 256 : name_err |= draw_line_breaker_append( &((*this_).linebr), &stereo_name, to_name );
83 256 : name_err |= utf8stream_writer_write_str( to_name, DRAW_CLASSIFIER_RIGHT_GUILLEMETS );
84 256 : const utf8stringview_t stereo = utf8stream_writemem_get_view( &((*this_).text_builder) );
85 :
86 : /* determine text width and height */
87 256 : pango_layout_set_font_description( font_layout, pencil_size_get_standard_font_description(pencil_size) );
88 512 : pango_layout_set_text( font_layout,
89 : utf8stringview_get_start( &stereo ),
90 256 : utf8stringview_get_length( &stereo )
91 : );
92 256 : pango_layout_set_width( font_layout, proposed_pango_width * PANGO_SCALE );
93 256 : pango_layout_get_pixel_size( font_layout, &text1_width, &text1_height) ;
94 256 : text1_height += PENCIL_SIZE_FONT_ALIGN_MARGIN; /* allow to align font with pixel border */
95 256 : text1_width += PENCIL_SIZE_FONT_ALIGN_MARGIN;
96 :
97 : /* restore pango context */
98 256 : pango_layout_set_width(font_layout, DRAW_CLASSIFIER_PANGO_UNLIMITED_WIDTH );
99 :
100 : /* cleanup the text_builder */
101 256 : name_err |= utf8stream_writemem_reset( &((*this_).text_builder) );
102 256 : if ( name_err != U8_ERROR_NONE )
103 : {
104 0 : U8_LOG_WARNING_HEX( "error at get_dim/draw_line_breaker_append", name_err );
105 : }
106 : }
107 : }
108 :
109 : /* draw name text */
110 256 : int text2_width = 0;
111 256 : int text2_height = 0;
112 : double space_for_line;
113 : {
114 : /* prepare text */
115 256 : u8_error_t name_err = U8_ERROR_NONE;
116 256 : utf8stream_writer_t *to_name = utf8stream_writemem_get_writer( &((*this_).text_builder) );
117 :
118 256 : if ( 0 != ( display_flags & DATA_DIAGRAMELEMENT_FLAG_ANONYMOUS_INSTANCE ) )
119 : {
120 0 : name_err |= utf8stream_writer_write_char( to_name, DRAW_CLASSIFIER_COLON );
121 : }
122 256 : utf8stringview_t class_name = UTF8STRINGVIEW_STR( data_classifier_get_name_const( classifier ) );
123 : /* insert linebreaks */
124 256 : name_err |= draw_line_breaker_append( &((*this_).linebr), &class_name, to_name );
125 256 : const utf8stringview_t name = utf8stream_writemem_get_view( &((*this_).text_builder) );
126 :
127 : /* determine text width and height */
128 256 : pango_layout_set_font_description( font_layout, pencil_size_get_title_font_description(pencil_size) );
129 512 : pango_layout_set_text( font_layout,
130 : utf8stringview_get_start( &name ),
131 256 : utf8stringview_get_length( &name )
132 : );
133 256 : pango_layout_set_width( font_layout, proposed_pango_width * PANGO_SCALE );
134 : /* option for nicer layout but harder to read: */
135 : /* pango_layout_set_wrap( font_layout, PANGO_WRAP_WORD_CHAR ); */
136 256 : pango_layout_get_pixel_size( font_layout, &text2_width, &text2_height );
137 256 : text2_height += PENCIL_SIZE_FONT_ALIGN_MARGIN; /* allow to align font with pixel border */
138 256 : text2_width += PENCIL_SIZE_FONT_ALIGN_MARGIN;
139 :
140 : /* restore pango context */
141 256 : pango_layout_set_width(font_layout, DRAW_CLASSIFIER_PANGO_UNLIMITED_WIDTH );
142 :
143 : /* for space between stereotype and name */
144 256 : text2_height += pencil_size_get_font_line_gap( pencil_size );
145 :
146 : /* for underscores under object instance names, add 2 * gap: */
147 256 : space_for_line = 2.0 * pencil_size_get_standard_object_border( pencil_size );
148 :
149 : /* cleanup the text_builder */
150 256 : name_err |= utf8stream_writemem_reset( &((*this_).text_builder) );
151 256 : if ( name_err != U8_ERROR_NONE )
152 : {
153 0 : U8_LOG_WARNING_HEX( "error at get_dim/draw_line_breaker_append", name_err );
154 : }
155 : }
156 :
157 : /* draw description text */
158 256 : int text3_width = 0;
159 256 : int text3_height = 0;
160 256 : if ( DATA_CLASSIFIER_TYPE_COMMENT == data_classifier_get_main_type ( classifier ) )
161 : {
162 8 : pango_layout_set_font_description (font_layout, pencil_size_get_standard_font_description(pencil_size) );
163 8 : pango_layout_set_text ( font_layout,
164 : data_classifier_get_description_const( classifier ),
165 : DRAW_CLASSIFIER_PANGO_AUTO_DETECT_LENGTH
166 : );
167 8 : pango_layout_get_pixel_size (font_layout, &text3_width, &text3_height);
168 8 : text3_height += PENCIL_SIZE_FONT_ALIGN_MARGIN; /* allow to align font with pixel border */
169 8 : text3_width += PENCIL_SIZE_FONT_ALIGN_MARGIN;
170 : }
171 :
172 256 : *out_label_dim = (geometry_dimensions_t) {
173 256 : .width = u8_i32_max3( text1_width, text2_width, text3_width ),
174 256 : .height = text1_height + text2_height + space_for_line + text3_height
175 : };
176 :
177 : /* maybe try a recursnio to improce/compactify the result dimensions? */
178 256 : const double proposed_width = geometry_dimensions_get_width( proposed_bounds );
179 256 : if (( max_recursions > 0 )&&( geometry_dimensions_get_width( out_label_dim ) > proposed_width ))
180 : {
181 64 : const geometry_dimensions_t new_proposed_bounds = *out_label_dim;
182 64 : draw_classifier_label_get_stereotype_and_name_dimensions( this_,
183 : visible_classifier,
184 : with_stereotype,
185 : &new_proposed_bounds,
186 : max_recursions-1,
187 : pencil_size,
188 : font_layout,
189 : out_label_dim
190 : );
191 : }
192 : }
193 : else
194 : {
195 0 : U8_LOG_ERROR("invalid visible classifier in draw_classifier_label_get_stereotype_and_name_dimensions()");
196 0 : *out_label_dim = (geometry_dimensions_t) { .width = 0.0, .height = 0.0 };
197 : }
198 256 : U8_TRACE_END();
199 256 : }
200 :
201 0 : void draw_classifier_label_draw_stereotype_and_name( draw_classifier_label_t *this_,
202 : const data_visible_classifier_t *visible_classifier,
203 : bool with_stereotype,
204 : const GdkRGBA *color,
205 : const geometry_rectangle_t *label_box,
206 : const pencil_size_t *pencil_size,
207 : PangoLayout *font_layout,
208 : cairo_t *cr )
209 : {
210 0 : U8_TRACE_BEGIN();
211 0 : assert( NULL != visible_classifier );
212 0 : assert( NULL != color );
213 0 : assert( NULL != label_box );
214 0 : assert( NULL != pencil_size );
215 0 : assert( NULL != font_layout );
216 0 : assert( NULL != cr );
217 :
218 : /* define names for input data: */
219 : const data_classifier_t *const classifier
220 0 : = data_visible_classifier_get_classifier_const( visible_classifier );
221 : const data_diagramelement_t *const diagramelement
222 0 : = data_visible_classifier_get_diagramelement_const( visible_classifier );
223 0 : const data_classifier_type_t classifier_type = data_classifier_get_main_type( classifier );
224 0 : const data_diagramelement_flag_t display_flags = data_diagramelement_get_display_flags( diagramelement );
225 :
226 : /* get layout sizes */
227 0 : const double left = geometry_rectangle_get_left( label_box );
228 0 : const double top = geometry_rectangle_get_top( label_box );
229 0 : const double width = geometry_rectangle_get_width( label_box );
230 0 : const double f_line_gap = pencil_size_get_font_line_gap( pencil_size );
231 0 : const double f_size = pencil_size_get_standard_font_size( pencil_size );
232 :
233 : /* draw stereotype text */
234 0 : int text1_height = 0;
235 : {
236 0 : if ( with_stereotype && data_classifier_has_stereotype( classifier ) )
237 : {
238 : /* prepare text */
239 0 : u8_error_t name_err = U8_ERROR_NONE;
240 0 : utf8stream_writer_t *to_name = utf8stream_writemem_get_writer( &((*this_).text_builder) );
241 0 : name_err |= utf8stream_writer_write_str( to_name, DRAW_CLASSIFIER_LEFT_GUILLEMETS );
242 0 : utf8stringview_t stereo_name = UTF8STRINGVIEW_STR( data_classifier_get_stereotype_const( classifier ) );
243 : /* insert linebreaks */
244 0 : name_err |= draw_line_breaker_append( &((*this_).linebr), &stereo_name, to_name );
245 0 : name_err |= utf8stream_writer_write_str( to_name, DRAW_CLASSIFIER_RIGHT_GUILLEMETS );
246 0 : const utf8stringview_t stereo = utf8stream_writemem_get_view( &((*this_).text_builder) );
247 :
248 : int text1_width;
249 0 : cairo_set_source_rgba( cr, color->red, color->green, color->blue, color->alpha );
250 0 : pango_layout_set_font_description( font_layout, pencil_size_get_standard_font_description(pencil_size) );
251 0 : pango_layout_set_text( font_layout,
252 : utf8stringview_get_start( &stereo ),
253 0 : utf8stringview_get_length( &stereo )
254 : );
255 0 : pango_layout_set_width( font_layout, (width+f_size) * PANGO_SCALE ); /* add gap to avoid line breaks by rounding errors and whitespace character widths */
256 0 : pango_layout_get_pixel_size (font_layout, &text1_width, &text1_height);
257 0 : cairo_move_to( cr,
258 0 : ceil( left + 0.5*( width - text1_width ) ),
259 : ceil( top )
260 : ); /* align font with pixel border */
261 0 : pango_cairo_show_layout( cr, font_layout );
262 :
263 : /* restore pango context */
264 0 : pango_layout_set_width(font_layout, DRAW_CLASSIFIER_PANGO_UNLIMITED_WIDTH );
265 :
266 : /* cleanup the text_builder */
267 0 : name_err |= utf8stream_writemem_reset( &((*this_).text_builder) );
268 0 : if ( name_err != U8_ERROR_NONE )
269 : {
270 0 : U8_LOG_WARNING_HEX( "error at draw/draw_line_breaker_append", name_err );
271 : }
272 : }
273 : }
274 :
275 : /* draw name text */
276 0 : int text2_height = 0;
277 : {
278 : /* prepare text */
279 0 : u8_error_t name_err = U8_ERROR_NONE;
280 0 : utf8stream_writer_t *to_name = utf8stream_writemem_get_writer( &((*this_).text_builder) );
281 :
282 0 : if ( 0 != ( display_flags & DATA_DIAGRAMELEMENT_FLAG_ANONYMOUS_INSTANCE ) )
283 : {
284 0 : name_err |= utf8stream_writer_write_char( to_name, DRAW_CLASSIFIER_COLON );
285 : }
286 0 : utf8stringview_t class_name = UTF8STRINGVIEW_STR( data_classifier_get_name_const( classifier ) );
287 : /* insert linebreaks */
288 0 : name_err |= draw_line_breaker_append( &((*this_).linebr), &class_name, to_name );
289 0 : const utf8stringview_t name = utf8stream_writemem_get_view( &((*this_).text_builder) );
290 :
291 : int text2_width;
292 0 : cairo_set_source_rgba( cr, color->red, color->green, color->blue, color->alpha );
293 0 : pango_layout_set_font_description( font_layout, pencil_size_get_title_font_description(pencil_size) );
294 0 : pango_layout_set_text( font_layout,
295 : utf8stringview_get_start( &name ),
296 0 : utf8stringview_get_length( &name )
297 : );
298 0 : pango_layout_set_width( font_layout, (width+f_size) * PANGO_SCALE ); /* add gap to avoid line breaks by rounding errors and whitespace character widths */
299 : /* option for nicer layout but harder to read: */
300 : /* pango_layout_set_wrap( font_layout, PANGO_WRAP_WORD_CHAR ); */
301 0 : pango_layout_get_pixel_size( font_layout, &text2_width, &text2_height );
302 :
303 : /* draw text */
304 0 : cairo_move_to( cr,
305 0 : ceil( left + 0.5*( width - text2_width ) ),
306 0 : ceil( top + text1_height + f_line_gap )
307 : ); /* align font with pixel border */
308 0 : pango_cairo_show_layout( cr, font_layout );
309 :
310 : /* restore pango context */
311 0 : pango_layout_set_width( font_layout, DRAW_CLASSIFIER_PANGO_UNLIMITED_WIDTH );
312 :
313 : /* underline instances */
314 0 : if ( 0 != ( display_flags & ( DATA_DIAGRAMELEMENT_FLAG_ANONYMOUS_INSTANCE | DATA_DIAGRAMELEMENT_FLAG_NAMED_INSTANCE ) ) )
315 : {
316 0 : cairo_move_to( cr, left + 0.5*( width - text2_width ), top+text1_height+f_line_gap+text2_height );
317 0 : cairo_line_to( cr, left + 0.5*( width + text2_width ), top+text1_height+f_line_gap+text2_height );
318 0 : cairo_stroke(cr);
319 : }
320 :
321 : /* cleanup the text_builder */
322 0 : name_err |= utf8stream_writemem_reset( &((*this_).text_builder) );
323 0 : if ( name_err != U8_ERROR_NONE )
324 : {
325 0 : U8_LOG_WARNING_HEX( "error at draw/draw_line_breaker_append", name_err );
326 : }
327 : }
328 :
329 : /* draw description text */
330 0 : if ( DATA_CLASSIFIER_TYPE_COMMENT == classifier_type )
331 : {
332 : int text3_width;
333 : int text3_height;
334 0 : cairo_set_source_rgba( cr, color->red, color->green, color->blue, color->alpha );
335 0 : pango_layout_set_font_description (font_layout, pencil_size_get_standard_font_description(pencil_size) );
336 0 : pango_layout_set_text ( font_layout,
337 : data_classifier_get_description_const( classifier ),
338 : DRAW_CLASSIFIER_PANGO_AUTO_DETECT_LENGTH
339 : );
340 0 : pango_layout_get_pixel_size (font_layout, &text3_width, &text3_height);
341 :
342 : /* draw text */
343 0 : cairo_move_to( cr,
344 0 : ceil( left + 0.5*( width - text3_width ) ),
345 0 : ceil( top + text1_height + f_line_gap + text2_height + f_line_gap )
346 : ); /* align font with pixel border */
347 0 : pango_cairo_show_layout( cr, font_layout );
348 : }
349 :
350 0 : U8_TRACE_END();
351 0 : }
352 :
353 :
354 : /*
355 : Copyright 2016-2026 Andreas Warnke
356 : http://www.apache.org/licenses/LICENSE-2.0
357 :
358 : Licensed under the Apache License, Version 2.0 (the "License");
359 : you may not use this file except in compliance with the License.
360 : You may obtain a copy of the License at
361 :
362 :
363 : Unless required by applicable law or agreed to in writing, software
364 : distributed under the License is distributed on an "AS IS" BASIS,
365 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
366 : See the License for the specific language governing permissions and
367 : limitations under the License.
368 : */
|