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