LCOV - code coverage report
Current view: top level - pencil/source/draw - draw_relationship_label.c (source / functions) Coverage Total Hit
Test: crystal-facet-uml_v1.71.2_covts Lines: 0.0 % 161 0
Test Date: 2026-08-22 19:47:36 Functions: 0.0 % 4 0

            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              : */
        

Generated by: LCOV version 2.0-1