Line data Source code
1 : /* File: draw_relationship_label.c; Copyright and License: see below */
2 :
3 : #include "draw/draw_relationship_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 "u8/u8_f64.h"
9 : #include "utf8stringbuf/utf8stringbuf.h"
10 : #include "utf8stringbuf/utf8string.h"
11 : #include <pango/pangocairo.h>
12 : #include <stdio.h>
13 : #include <stdlib.h>
14 : #include <assert.h>
15 :
16 : static const int DRAW_RELATIONSHIP_PANGO_UNLIMITED_WIDTH = -1;
17 : #define DRAW_RELATIONSHIP_LEFT_GUILLEMETS "\xc2\xab"
18 : #define DRAW_RELATIONSHIP_RIGHT_GUILLEMETS "\xc2\xbb"
19 :
20 0 : void draw_relationship_label_init( draw_relationship_label_t *this_ )
21 : {
22 0 : utf8stream_writemem_init( &((*this_).text_builder), &((*this_).text_buffer), sizeof( (*this_).text_buffer) );
23 0 : draw_line_breaker_init( &((*this_).linebr) );
24 0 : draw_stereotype_icon_init( &((*this_).image_renderer) );
25 0 : }
26 :
27 0 : void draw_relationship_label_destroy( draw_relationship_label_t *this_ )
28 : {
29 0 : draw_line_breaker_destroy( &((*this_).linebr) );
30 0 : const u8_error_t text_err = utf8stream_writemem_destroy( &((*this_).text_builder) );
31 0 : if ( text_err != U8_ERROR_NONE )
32 : {
33 0 : U8_LOG_WARNING_HEX( "error at draw/draw_classifier_label: buffer too small", text_err );
34 : }
35 0 : draw_stereotype_icon_destroy( &((*this_).image_renderer) );
36 0 : }
37 :
38 0 : void draw_relationship_label_get_type_and_name_dimensions ( draw_relationship_label_t *this_,
39 : const data_relationship_t *relationship,
40 : const data_profile_part_t *profile,
41 : const geometry_dimensions_t *proposed_bounds,
42 : const pencil_size_t *pencil_size,
43 : PangoLayout *font_layout,
44 : geometry_dimensions_t *out_label_dim )
45 : {
46 0 : U8_TRACE_BEGIN();
47 0 : assert( NULL != relationship );
48 0 : assert( NULL != profile );
49 0 : assert( NULL != proposed_bounds );
50 0 : assert( NULL != pencil_size );
51 0 : assert( NULL != font_layout );
52 0 : assert( NULL != out_label_dim );
53 :
54 0 : if ( data_relationship_is_valid( relationship ) )
55 : {
56 : /* calc stereotype image bounds */
57 0 : const char *const relationship_stereotype = data_relationship_get_stereotype_const( relationship );
58 : const bool has_stereotype_image
59 0 : = draw_stereotype_icon_exists( &((*this_).image_renderer), relationship_stereotype, profile );
60 0 : const geometry_dimensions_t icon_dim
61 : = has_stereotype_image
62 0 : ? draw_stereotype_icon_get_dimensions( &((*this_).image_renderer), pencil_size )
63 0 : : (geometry_dimensions_t){ .width = 0.0, .height = 0.0 };
64 0 : const double icon_gap = has_stereotype_image ? pencil_size_get_standard_object_border( pencil_size ) : 0.0;
65 :
66 : /* define names for input data */
67 0 : int proposed_pango_width = geometry_dimensions_get_width( proposed_bounds );
68 0 : const double f_line_gap = pencil_size_get_font_line_gap( pencil_size );
69 :
70 : /* calc dimensions of stereotype as text */
71 0 : int text1_height = 0;
72 0 : int text1_width = 0;
73 : {
74 0 : const data_relationship_type_t rel_type = data_relationship_get_main_type( relationship );
75 : const char *const pseudo_stereotype
76 0 : = draw_relationship_label_private_stereotype_from_type( this_, rel_type );
77 0 : const bool has_pseudo_stereotype = ( ! utf8string_equals_str( pseudo_stereotype, "" ) );
78 0 : const bool has_stereotype = ( ! utf8string_equals_str( relationship_stereotype, "" ) );
79 :
80 0 : if ( ( ! has_stereotype_image ) && ( has_pseudo_stereotype || has_stereotype ) )
81 : {
82 0 : u8_error_t name_err = U8_ERROR_NONE;
83 0 : utf8stream_writer_t *to_name = utf8stream_writemem_get_writer( &((*this_).text_builder) );
84 :
85 : /* append parts to name and insert linebreaks */
86 0 : name_err |= utf8stream_writer_write_str( to_name, DRAW_RELATIONSHIP_LEFT_GUILLEMETS );
87 :
88 0 : if ( has_stereotype )
89 : {
90 0 : utf8stringview_t stereo_name = UTF8STRINGVIEW_STR( relationship_stereotype );
91 0 : name_err |= draw_line_breaker_append( &((*this_).linebr), &stereo_name, to_name );
92 : }
93 : else
94 : {
95 : /* there are no line-break proposals in the pre-defined set of pseudo_stereotype */
96 0 : name_err |= utf8stream_writer_write_str( to_name, pseudo_stereotype );
97 : }
98 0 : name_err |= utf8stream_writer_write_str( to_name, DRAW_RELATIONSHIP_RIGHT_GUILLEMETS );
99 0 : const utf8stringview_t stereo = utf8stream_writemem_get_view( &((*this_).text_builder) );
100 :
101 : #if 0
102 : /* prepare text */
103 : if ( has_stereotype )
104 : {
105 : utf8stringbuf_append_str( &stereotype_buf, relationship_stereotype );
106 : }
107 : else
108 : {
109 : utf8stringbuf_append_str( &stereotype_buf, pseudo_stereotype );
110 : }
111 : #endif
112 :
113 : /* determine text width and height */
114 0 : pango_layout_set_font_description( font_layout, pencil_size_get_footnote_font_description( pencil_size ) );
115 0 : pango_layout_set_text( font_layout,
116 : utf8stringview_get_start( &stereo ),
117 0 : utf8stringview_get_length( &stereo )
118 : );
119 0 : pango_layout_set_width( font_layout, proposed_pango_width * PANGO_SCALE );
120 0 : pango_layout_get_pixel_size( font_layout, &text1_width, &text1_height );
121 0 : text1_height += PENCIL_SIZE_FONT_ALIGN_MARGIN; /* allow to align font with pixel border */
122 0 : text1_width += PENCIL_SIZE_FONT_ALIGN_MARGIN;
123 :
124 : /* restore pango context */
125 0 : pango_layout_set_width(font_layout, DRAW_RELATIONSHIP_PANGO_UNLIMITED_WIDTH);
126 :
127 : /* cleanup the text_builder */
128 0 : name_err |= utf8stream_writemem_reset( &((*this_).text_builder) );
129 0 : if ( name_err != U8_ERROR_NONE )
130 : {
131 0 : U8_LOG_WARNING_HEX( "error at get_dim/draw_line_breaker_append", name_err );
132 : }
133 : }
134 : }
135 :
136 : /* calc name text dimensions */
137 0 : int text2_height = 0;
138 0 : int text2_width = 0;
139 0 : if ( 0 != utf8string_get_length( data_relationship_get_name_const( relationship ) ))
140 : {
141 0 : u8_error_t name_err = U8_ERROR_NONE;
142 0 : utf8stream_writer_t *to_name = utf8stream_writemem_get_writer( &((*this_).text_builder) );
143 :
144 : /* append parts to name and insert linebreaks */
145 0 : utf8stringview_t rel_name = UTF8STRINGVIEW_STR( data_relationship_get_name_const( relationship ) );
146 0 : name_err |= draw_line_breaker_append( &((*this_).linebr), &rel_name, to_name );
147 0 : const utf8stringview_t name = utf8stream_writemem_get_view( &((*this_).text_builder) );
148 :
149 0 : pango_layout_set_font_description( font_layout, pencil_size_get_standard_font_description(pencil_size) );
150 0 : pango_layout_set_text( font_layout,
151 : utf8stringview_get_start( &name ),
152 0 : utf8stringview_get_length( &name )
153 : );
154 0 : pango_layout_set_width( font_layout, proposed_pango_width * PANGO_SCALE );
155 0 : pango_layout_get_pixel_size( font_layout, &text2_width, &text2_height );
156 0 : text2_height += PENCIL_SIZE_FONT_ALIGN_MARGIN; /* allow to align font with pixel border */
157 0 : text2_width += PENCIL_SIZE_FONT_ALIGN_MARGIN;
158 :
159 : /* restore pango context */
160 0 : pango_layout_set_width(font_layout, DRAW_RELATIONSHIP_PANGO_UNLIMITED_WIDTH);
161 :
162 : /* cleanup the text_builder */
163 0 : name_err |= utf8stream_writemem_reset( &((*this_).text_builder) );
164 0 : if ( name_err != U8_ERROR_NONE )
165 : {
166 0 : U8_LOG_WARNING_HEX( "error at get_dim/draw_line_breaker_append", name_err );
167 : }
168 : }
169 :
170 0 : *out_label_dim = (geometry_dimensions_t){
171 0 : .width = geometry_dimensions_get_width( &icon_dim ) + icon_gap + u8_i32_max2( text2_width, text1_width ),
172 0 : .height = u8_f64_max2( geometry_dimensions_get_height( &icon_dim ), text1_height + f_line_gap + text2_height )
173 : };
174 : }
175 : else
176 : {
177 0 : U8_LOG_ERROR("invalid relationship in draw_relationship_label_get_type_and_name_dimensions()");
178 0 : *out_label_dim = (geometry_dimensions_t) { .width = 0.0, .height = 0.0 };
179 : }
180 0 : U8_TRACE_END();
181 0 : }
182 :
183 0 : void draw_relationship_label_draw_type_and_name ( draw_relationship_label_t *this_,
184 : const data_relationship_t *relationship,
185 : const data_profile_part_t *profile,
186 : const GdkRGBA *color,
187 : const geometry_rectangle_t *label_box,
188 : const pencil_size_t *pencil_size,
189 : PangoLayout *font_layout,
190 : cairo_t *cr )
191 : {
192 0 : U8_TRACE_BEGIN();
193 0 : assert( NULL != relationship );
194 0 : assert( NULL != profile );
195 0 : assert( NULL != color );
196 0 : assert( NULL != label_box );
197 0 : assert( NULL != pencil_size );
198 0 : assert( NULL != font_layout );
199 0 : assert( NULL != cr );
200 :
201 : /* calc bounds of stereotype icon */
202 0 : const char *const relationship_stereotype = data_relationship_get_stereotype_const( relationship );
203 : const bool has_stereotype_image
204 0 : = draw_stereotype_icon_exists( &((*this_).image_renderer), relationship_stereotype, profile );
205 0 : const geometry_rectangle_t stereotype_box
206 : = has_stereotype_image
207 0 : ? draw_stereotype_icon_get_bounds( &((*this_).image_renderer),
208 : geometry_rectangle_get_left( label_box ),
209 : geometry_rectangle_get_top( label_box ),
210 : GEOMETRY_H_ALIGN_LEFT,
211 : GEOMETRY_V_ALIGN_TOP,
212 : pencil_size
213 : )
214 0 : : (geometry_rectangle_t){ .left = 0.0, .top = 0.0, .width = 0.0, .height = 0.0 };
215 0 : const double icon_gap = has_stereotype_image ? pencil_size_get_standard_object_border( pencil_size ) : 0.0;
216 :
217 : /* draw stereotype icon */
218 0 : if ( has_stereotype_image )
219 : {
220 : u8_error_info_t err_info;
221 : const u8_error_t stereotype_err
222 0 : = draw_stereotype_icon_draw( &((*this_).image_renderer),
223 : relationship_stereotype,
224 : profile,
225 : color,
226 : &err_info,
227 : &stereotype_box,
228 : cr
229 : );
230 0 : if ( u8_error_info_is_error( &err_info ) )
231 : {
232 0 : U8_LOG_WARNING_INT( "stereotype image: unxpected token in svg path in line",
233 : u8_error_info_get_line( &err_info )
234 : );
235 : }
236 0 : else if ( stereotype_err != U8_ERROR_NONE )
237 : {
238 0 : U8_LOG_WARNING_HEX( "error at drawing stereotype image", stereotype_err );
239 : }
240 : }
241 :
242 : /* define names for input data */
243 0 : const double text_width
244 0 : = geometry_rectangle_get_width( label_box ) - geometry_rectangle_get_width( &stereotype_box ) - icon_gap;
245 0 : const double center_x
246 0 : = geometry_rectangle_get_left( label_box ) + geometry_rectangle_get_width( &stereotype_box ) + icon_gap
247 0 : + 0.5 * text_width;
248 0 : const double top = geometry_rectangle_get_top( label_box );
249 0 : const double f_line_gap = pencil_size_get_font_line_gap( pencil_size );
250 :
251 : /* draw stereotype as text */
252 0 : int text1_height = 0;
253 : {
254 0 : const data_relationship_type_t rel_type = data_relationship_get_main_type( relationship );
255 : const char *const pseudo_stereotype
256 0 : = draw_relationship_label_private_stereotype_from_type( this_, rel_type );
257 0 : const bool has_pseudo_stereotype = ( ! utf8string_equals_str( pseudo_stereotype, "" ) );
258 0 : const bool has_stereotype = ( ! utf8string_equals_str( relationship_stereotype, "" ) );
259 :
260 0 : if ( ( ! has_stereotype_image ) && ( has_pseudo_stereotype || has_stereotype ) )
261 : {
262 0 : u8_error_t name_err = U8_ERROR_NONE;
263 0 : utf8stream_writer_t *to_name = utf8stream_writemem_get_writer( &((*this_).text_builder) );
264 :
265 : /* append parts to name and insert linebreaks */
266 0 : name_err |= utf8stream_writer_write_str( to_name, DRAW_RELATIONSHIP_LEFT_GUILLEMETS );
267 0 : if ( has_stereotype )
268 : {
269 0 : utf8stringview_t stereo_name = UTF8STRINGVIEW_STR( relationship_stereotype );
270 0 : name_err |= draw_line_breaker_append( &((*this_).linebr), &stereo_name, to_name );
271 : }
272 : else
273 : {
274 : /* there are no line-break proposals in the pre-defined set of pseudo_stereotype */
275 0 : name_err |= utf8stream_writer_write_str( to_name, pseudo_stereotype );
276 : }
277 :
278 0 : name_err |= utf8stream_writer_write_str( to_name, DRAW_RELATIONSHIP_RIGHT_GUILLEMETS );
279 0 : const utf8stringview_t stereo = utf8stream_writemem_get_view( &((*this_).text_builder) );
280 :
281 : #if 0
282 : /* prepare text */
283 : if ( has_stereotype )
284 : {
285 : utf8stringbuf_append_str( &stereotype_buf, relationship_stereotype );
286 : }
287 : else
288 : {
289 : utf8stringbuf_append_str( &stereotype_buf, pseudo_stereotype );
290 : }
291 : #endif
292 :
293 : int text1_width;
294 0 : const double f_size = pencil_size_get_standard_font_size( pencil_size );
295 0 : cairo_set_source_rgba( cr, color->red, color->green, color->blue, color->alpha );
296 0 : pango_layout_set_font_description( font_layout, pencil_size_get_footnote_font_description( pencil_size ) );
297 0 : pango_layout_set_text( font_layout,
298 : utf8stringview_get_start( &stereo ),
299 0 : utf8stringview_get_length( &stereo )
300 : );
301 0 : pango_layout_set_width(font_layout, (text_width+f_size) * PANGO_SCALE ); /* add gap to avoid line breaks by rounding errors and whitespace character widths */
302 0 : pango_layout_get_pixel_size( font_layout, &text1_width, &text1_height );
303 :
304 : /* draw text */
305 0 : cairo_move_to( cr, ceil( center_x - 0.5*text1_width ), ceil( top ) ); /* align font with pixel border */
306 0 : pango_cairo_show_layout( cr, font_layout );
307 :
308 : /* restore pango context */
309 0 : pango_layout_set_width(font_layout, DRAW_RELATIONSHIP_PANGO_UNLIMITED_WIDTH);
310 :
311 : /* cleanup the text_builder */
312 0 : name_err |= utf8stream_writemem_reset( &((*this_).text_builder) );
313 0 : if ( name_err != U8_ERROR_NONE )
314 : {
315 0 : U8_LOG_WARNING_HEX( "error at get_dim/draw_line_breaker_append", name_err );
316 : }
317 : }
318 : }
319 :
320 : /* draw name text */
321 0 : if ( 0 != utf8string_get_length( data_relationship_get_name_const( relationship ) ))
322 : {
323 0 : u8_error_t name_err = U8_ERROR_NONE;
324 0 : utf8stream_writer_t *to_name = utf8stream_writemem_get_writer( &((*this_).text_builder) );
325 :
326 : /* append parts to name and insert linebreaks */
327 0 : utf8stringview_t rel_name = UTF8STRINGVIEW_STR( data_relationship_get_name_const( relationship ) );
328 0 : name_err |= draw_line_breaker_append( &((*this_).linebr), &rel_name, to_name );
329 0 : const utf8stringview_t name = utf8stream_writemem_get_view( &((*this_).text_builder) );
330 :
331 : int text2_height;
332 : int text2_width;
333 0 : const double f_size = pencil_size_get_standard_font_size( pencil_size );
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 : utf8stringview_get_start( &name ),
338 0 : utf8stringview_get_length( &name )
339 : );
340 0 : pango_layout_set_width(font_layout, (text_width+f_size) * PANGO_SCALE ); /* add gap to avoid line breaks by rounding errors and whitespace character widths */
341 0 : pango_layout_get_pixel_size (font_layout, &text2_width, &text2_height);
342 :
343 : /* draw text */
344 0 : cairo_move_to( cr,
345 0 : ceil( center_x - 0.5*text2_width ),
346 0 : ceil( top + text1_height + f_line_gap )
347 : ); /* align font with pixel border */
348 0 : pango_cairo_show_layout( cr, font_layout );
349 :
350 : /* restore pango context */
351 0 : pango_layout_set_width(font_layout, DRAW_RELATIONSHIP_PANGO_UNLIMITED_WIDTH);
352 :
353 : /* cleanup the text_builder */
354 0 : name_err |= utf8stream_writemem_reset( &((*this_).text_builder) );
355 0 : if ( name_err != U8_ERROR_NONE )
356 : {
357 0 : U8_LOG_WARNING_HEX( "error at get_dim/draw_line_breaker_append", name_err );
358 : }
359 : }
360 :
361 0 : U8_TRACE_END();
362 0 : }
363 :
364 :
365 : /*
366 : Copyright 2017-2026 Andreas Warnke
367 : http://www.apache.org/licenses/LICENSE-2.0
368 :
369 : Licensed under the Apache License, Version 2.0 (the "License");
370 : you may not use this file except in compliance with the License.
371 : You may obtain a copy of the License at
372 :
373 :
374 : Unless required by applicable law or agreed to in writing, software
375 : distributed under the License is distributed on an "AS IS" BASIS,
376 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
377 : See the License for the specific language governing permissions and
378 : limitations under the License.
379 : */
|