Page MenuHomePhabricator

evas_textblock: content fit feature
Needs ReviewPublic

Authored by ali.alzyod on Jul 11 2019, 4:57 AM.

Details

Summary

Content Fit Feature for Evas_Object_Textblock

This Feature is available at Evas object level. And Edje level (where it is internally use evas functionality)
This feature will allow text block to fit its content font size to proper size to fit its area.

Main Properties:
Fit Modes : None=Default, Width, Height, All [Width+Height]
Fit Size Range : Contains maximum and minimum font size to be used (and in between).
Fit Step Size : Step(Jump) value when trying fonts sizes between Size_Range max and min.
Fit Size Array : Other way to resize font, where you explicitly select font sizes to be uses (for example [20, 50, 100] it will try 3 sizes only)

Text Fit feature was available in Edje but:
1- It doesn't effected by ellipsis or warping in font style (or do not handle the in right way)
2- Accuracy is not good (specially if you have fix pixel size elements (spaces,tabs,items))
3- No (Step size, Size Array) available.

Test Plan

To check the Feature

elementary_test
fit
textbock fit

You can modify all the modes and properties

These are two examples, One using Evas other uses Edje

Evas

#include <Elementary.h>

enum BUTTON{
   BUTTON_MODE             = 0,
   BUTTON_MAX              = 1,
   BUTTON_MIN              = 2,
   BUTTON_STEP             = 3,
   BUTTON_ARRAY            = 4,
   BUTTON_CONTENT          = 5,
   BUTTON_STYLE            = 6,
   BUTTON_ALL              = BUTTON_STYLE+1,
};

char* BUTTON_STR[BUTTON_ALL] ={
   "MODE",
   "MAX",
   "MIN",
   "STEP",
   "ARRAY",
   "CONTENT",
   "STYLE",
};

char *contents[] = {
   "Hello World",
   "This is Line<br>THis is other Line",
   "This text contains <font_size=20 color=#F00>SPECIFIC SIZE</font_size> that does not effected by fit mode"
   };

char *styles[] = {
   "DEFAULT='font=sans font_size=30 color=#000 wrap=mixed ellipsis=1.0'",
   "DEFAULT='font=sans font_size=30 color=#000 wrap=mixed'",
   "DEFAULT='font=sans font_size=30 color=#000 ellipsis=1.0'",
   "DEFAULT='font=sans font_size=30 color=#000'",
   };

char *styles_names[] = {
   "wrap=<color=#F00>mixed</color> ellipsis=<color=#F00>1.0</color>",
   "wrap=<color=#F00>mixed</color> ellipsis=<color=#F00>NONE</color>",
   "wrap=<color=#F00>NONE</color> ellipsis=<color=#F00>1.0</color>",
   "wrap=<color=#F00>NONE</color> ellipsis=<color=#F00>NONE</color>",
   };

typedef struct _APP
{
   Evas_Object *win, *box, *txtblock,*bg, *boxHor, *boxHor2;
   Eo *btn[BUTTON_ALL];
   Eo *lbl_status;
   char * str;
   unsigned int i_contnet, i_style;
} APP;
APP *app;

char * get_fit_status(Eo * textblock);

static void _btn_clicked(void *data EINA_UNUSED, Eo *obj, void *eventInfo EINA_UNUSED){
   if (obj == app->btn[BUTTON_MODE])
     {
        unsigned int options;
        evas_textblock_fit_options_get(app->txtblock, &options);
        if (options == TEXTBLOCK_FIT_MODE_NONE)
           evas_textblock_fit_options_set(app->txtblock, TEXTBLOCK_FIT_MODE_HEIGHT);
        else if (options == TEXTBLOCK_FIT_MODE_HEIGHT)
           evas_textblock_fit_options_set(app->txtblock, TEXTBLOCK_FIT_MODE_WIDTH);
        else if (options == TEXTBLOCK_FIT_MODE_WIDTH)
           evas_textblock_fit_options_set(app->txtblock, TEXTBLOCK_FIT_MODE_ALL);
        else if (options == TEXTBLOCK_FIT_MODE_ALL)
           evas_textblock_fit_options_set(app->txtblock, TEXTBLOCK_FIT_MODE_NONE);
     }
   else if (obj == app->btn[BUTTON_MAX])
     {
        unsigned int min, max;
        evas_textblock_fit_size_range_get(app->txtblock, &min, &max);
        max -= 5;
        evas_textblock_fit_size_range_set(app->txtblock, min, max);
     }
   else if (obj == app->btn[BUTTON_MIN])
     {
        unsigned int min, max;
        evas_textblock_fit_size_range_get(app->txtblock, &min, &max);
        min += 5;
        evas_textblock_fit_size_range_set(app->txtblock, min, max);
     }
   else if (obj == app->btn[BUTTON_STEP])
     {
        unsigned int step;
        evas_textblock_fit_step_size_get(app->txtblock, &step);
        step++;
        evas_textblock_fit_step_size_set(app->txtblock, step);
     }
   else if (obj == app->btn[BUTTON_ARRAY])
     {
        unsigned int font_size[] = {10, 50, 100 ,150};
        evas_textblock_fit_size_array_set(app->txtblock,font_size,4);
     }
   else if (obj == app->btn[BUTTON_CONTENT])
     {
        app->i_contnet++;
        if(app->i_contnet>=sizeof(contents)/sizeof(char*))
           app->i_contnet=0;
        evas_object_textblock_text_markup_set(app->txtblock,contents[app->i_contnet]);
     }
   else if (obj == app->btn[BUTTON_STYLE])
     {
        app->i_style++;
        if(app->i_style>=sizeof(styles)/sizeof(char*))
           app->i_style=0;

        Evas_Textblock_Style *style = evas_object_textblock_style_get(app->txtblock);
        evas_textblock_style_set(style,styles[app->i_style]);
     }

   elm_object_text_set(app->lbl_status, get_fit_status(app->txtblock));
}

char * get_fit_status(Eo * textblock)
{
   static char status[0xFFF];
   unsigned int options,min,max,step,size_array[256];
   size_t size_array_len;
   evas_textblock_fit_options_get(textblock,&options);
   evas_textblock_fit_size_range_get(textblock,&min,&max);
   evas_textblock_fit_step_size_get(textblock,&step);
   evas_textblock_fit_size_array_get(textblock,NULL,&size_array_len,0);
   if (size_array_len>255)
      size_array_len = 255;
   evas_textblock_fit_size_array_get(textblock,size_array,NULL,size_array_len);

   strcpy(status,"Mode : ");
   if (options == TEXTBLOCK_FIT_MODE_NONE)
      strcat(status,"MODE_NONE");
   else if (options == TEXTBLOCK_FIT_MODE_HEIGHT)
      strcat(status,"MODE_HEIGHT");
   else if (options == TEXTBLOCK_FIT_MODE_WIDTH)
      strcat(status,"MODE_WIDTH");
   else if (options == TEXTBLOCK_FIT_MODE_ALL)
      strcat(status,"MODE_ALL");

   strcat(status,"<br>");
   sprintf(status + strlen(status),"Max   : %d<br>",max);
   sprintf(status + strlen(status),"Min   : %d<br>",min);
   sprintf(status + strlen(status),"Step  : %d<br>",step);
   sprintf(status + strlen(status),"Array  : [ ");
   for (size_t i = 0 ; i < 10 ; i++)
     {
        if(i<size_array_len)
           sprintf(status + strlen(status)," %d,",size_array[i]);
     }

   if(10<size_array_len)
      sprintf(status + strlen(status)," ... ");
   sprintf(status + strlen(status)," ]");

   sprintf(status + strlen(status),"<br>");
   sprintf(status + strlen(status),"%s",styles_names[app->i_style]);



   return status;
}

int elm_main(int argc, char **argv)
{
  app = calloc(sizeof(APP), 1);

   elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);

   app->win = elm_win_util_standard_add("Main", "App");
   elm_win_autodel_set(app->win, EINA_TRUE);

   app->box = elm_box_add(app->win);
   app->boxHor = elm_box_add(app->box);
   app->boxHor2 = elm_box_add(app->box);
   app->txtblock = evas_object_textblock_add(app->box);
   app->bg = elm_bg_add(app->box);
   elm_bg_color_set(app->bg,255,255,255);

   Evas_Textblock_Style *style = evas_textblock_style_new();
   evas_textblock_style_set(style,styles[0]);
   evas_object_textblock_style_set(app->txtblock,style);
   evas_object_textblock_text_markup_set(app->txtblock,contents[0]);

   elm_box_horizontal_set(app->boxHor, EINA_TRUE);
   elm_box_horizontal_set(app->boxHor2, EINA_TRUE);

   evas_object_size_hint_weight_set(app->box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(app->box, EVAS_HINT_FILL, EVAS_HINT_FILL);


   evas_object_size_hint_weight_set(app->box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(app->box, EVAS_HINT_FILL, EVAS_HINT_FILL);

   evas_object_show(app->txtblock);
   evas_object_show(app->bg);
   evas_object_show(app->box);
   evas_object_show(app->boxHor);
   evas_object_show(app->boxHor2);

   elm_box_pack_end(app->box, app->bg);
   elm_box_pack_end(app->box, app->boxHor);
   elm_box_pack_end(app->box, app->boxHor2);

   elm_object_content_set(app->bg,app->txtblock);

   elm_win_resize_object_add(app->win, app->box);
   evas_object_resize(app->win, 320, 480);

   for(int i = 0 ; i < BUTTON_ALL ; i++)
     {
        app->btn[i] = elm_button_add(app->boxHor);
        evas_object_smart_callback_add(app->btn[i], "clicked", _btn_clicked, NULL);
        elm_object_text_set(app->btn[i], BUTTON_STR[i]);
        elm_box_pack_end(app->boxHor, app->btn[i]);
        evas_object_show(app->btn[i]);
     }

   app->lbl_status = elm_label_add(app->boxHor2);
   elm_object_text_set(app->lbl_status, get_fit_status(app->txtblock));
   elm_box_pack_end(app->boxHor2, app->lbl_status);
   evas_object_show(app->lbl_status);

   evas_object_size_hint_weight_set(app->txtblock, EVAS_HINT_EXPAND,EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(app->txtblock, EVAS_HINT_FILL, EVAS_HINT_FILL);

   evas_object_size_hint_weight_set(app->bg, EVAS_HINT_EXPAND,EVAS_HINT_EXPAND);
   evas_object_size_hint_align_set(app->bg, EVAS_HINT_FILL, EVAS_HINT_FILL);

   evas_object_show(app->win);
   elm_run();
   return 0;
}

ELM_MAIN()

Edje

// compile: edje_cc source.edc
// run: edje_player source.edje
collections {
   styles 
   {
      style 
      { 
         name: "text_style";
         base: "font=sans font_size=30 color=#FFF wrap=mixed ellipsis=1.0";
         tag: "br" "\n";
         tag: "ps" "ps";
         tag: "tab" "\t";
         tag: "b" "+ font_weight=Bold";
      }
   }
   group {
      name: "my_group"; // must be the same as in source.c
      parts {

         part 
         { 
            name: "background";
            type: RECT;
            scale: 1;
            description 
            {
               color: 0 0 0 0;
               rel1.relative: 0.0 0.0;
               rel2.relative: 1.0 1.0;
            }
         }

         part 
         { 
            name: "text";
            type: TEXTBLOCK;
            scale: 1;
            entry_mode: NONE;
            effect: OUTLINE_SHADOW;
            description 
            { 
               state: "default" 0.0;
               rel1.to : "background";
               rel1.relative: 0.0 0.0;
               rel2.to : "background";
               rel2.relative: 1.0 1.0;
               text 
               {
                  style: "text_style";
                  align: 0.0 0.0;
                  text: "Hello World This is Me";
                  fit: 1 1;
                  fit_step: 1;
                  size_range: 30 200;
                  //fit_size_array: 20 40 60 80 100 200;
               }
            }
         }
      }
   }
}

Found Task T5724 relative to this Feature

Diff Detail

Repository
rEFL core/efl
Branch
arcpatch-D9280
Lint
No Linters Available
Unit
No Unit Test Coverage
Build Status
Buildable 14543
Build 10002: arc lint + arc unit
ali.alzyod created this revision.Jul 11 2019, 4:57 AM

It seems that this patch has no reviewers specified. If you are unsure who can review your patch, please check this wiki page and see if anyone can be added: https://phab.enlightenment.org/w/maintainers_reviewers/

ali.alzyod requested review of this revision.Jul 11 2019, 4:57 AM
ali.alzyod abandoned this revision.Jul 11 2019, 4:59 AM
ali.alzyod changed the visibility from "Public (No Login Required)" to "No One".
ali.alzyod removed subscribers: cedric, reviewers, committers.
  • update edje part
ali.alzyod updated this revision to Diff 23383.Jul 15 2019, 9:44 PM
  • fix function name, update test
ali.alzyod updated this revision to Diff 23384.Jul 16 2019, 1:06 AM
  • change comment
ali.alzyod edited the summary of this revision. (Show Details)Jul 16 2019, 1:17 AM
ali.alzyod edited the summary of this revision. (Show Details)Jul 16 2019, 7:14 AM
ali.alzyod edited the summary of this revision. (Show Details)Jul 16 2019, 7:21 AM
ali.alzyod edited the test plan for this revision. (Show Details)
ali.alzyod changed the visibility from "No One" to "Public (No Login Required)".Jul 16 2019, 10:56 PM
ali.alzyod added reviewers: cedric, raster.

@cedric @raster
Are there way to pass int array in EDC file as a value for parameter.
Currently I am using string in EDC file (for example "1 2 3 4") then parse it in c to int array

ali.alzyod edited the test plan for this revision. (Show Details)Jul 22 2019, 5:50 AM
ali.alzyod edited the test plan for this revision. (Show Details)
ali.alzyod edited the summary of this revision. (Show Details)

update for optimizations to reduce calculations:
1- When Textblock width change in height_fit mode
2- When Textblock height change in width_fit mode
3- when font size_range change and we keep save fit size
4- when Textblock size increase after reach max fit size
5- when Textblock size reduced after reach min fit size

ali.alzyod updated this revision to Diff 23576.Jul 23 2019, 4:39 AM

1- Keep internal markup_text string while fitting
2- Fix reduce calculations condiftion when max or min font size reached
3- Fix reduce calculations for min size for rendering ellipsis

ali.alzyod edited the test plan for this revision. (Show Details)Jul 23 2019, 4:46 AM
ali.alzyod updated this revision to Diff 23578.Jul 23 2019, 5:10 AM

clear last selected fit index when setting new array

ali.alzyod edited the test plan for this revision. (Show Details)Jul 27 2019, 11:36 PM
cedric requested changes to this revision.Jul 29 2019, 10:39 AM

Pretty cool stuff. I would just prefer a loop with parse_int instead of a string.

src/bin/edje/edje_cc_handlers.c
11729

I would prefer to see a parse_int in a loop until it fails instead of a string.

This revision now requires changes to proceed.Jul 29 2019, 10:39 AM

Pretty cool stuff. I would just prefer a loop with parse_int instead of a string.

I wish to know how to use int array instead of string.
Now I am using in EDC:

fit_size_array: "20 40 60 80 100 200"

and it should be:

fit_size_array: 20 40 60 80 100 200
woohyun added a comment.EditedSep 4 2019, 10:58 PM

@cedric

Could you give a guideline ( (to @ali.alzyod) to how to use int array ?

This comment was removed by ali.alzyod.

@ali.alzyod

I also think parse_int(i) ("i" would be increased till the end of the input) can be used instead of string.

In edje_cc_handler.c , you can check how to use parse_int() easily :)

@ali.alzyod

I also think parse_int(i) ("i" would be increased till the end of the input) can be used instead of string.

In edje_cc_handler.c , you can check how to use parse_int() easily :)

The problem is where to save these values? and how to load them again.
I can parse int, but I am struggling in saving these values and loading them.
Also I could not find other similar feature used by Edje to load int array.

@ali.alzyod

I also think parse_int(i) ("i" would be increased till the end of the input) can be used instead of string.

In edje_cc_handler.c , you can check how to use parse_int() easily :)

The problem is where to save these values? and how to load them again.
I can parse int, but I am struggling in saving these values and loading them.
Also I could not find other similar feature used by Edje to load int array.

Hmm. I can understand your point now - That array does not have limitation about the number of inputs. Am I right ?
Then, it should be a BIG array or dynamically allocated following the number of input. (and should be freed properly).

@cedric
I also cannot find out the similar implementation with this case, so could you share your idea with a bit more details ?

@woohyun
Exactly, what data EDC/EINA type we should use to compile/decompile EDC files into Edje, and still be able to read these values.

@cedric

Could you give guideline how to implement int array ?
If you don't have any specific idea on that, then I think we need to go without this feature for a while.

ali.alzyod updated this revision to Diff 26433.Oct 28 2019, 6:15 AM

use int array instead of string

ali.alzyod edited the test plan for this revision. (Show Details)Oct 28 2019, 6:16 AM

@woohyun @cedric
Now we are using int array instead of string

ali.alzyod marked an inline comment as done.Oct 28 2019, 6:18 AM
woohyun accepted this revision.Oct 31 2019, 11:58 PM
woohyun requested changes to this revision.Nov 1 2019, 12:02 AM

Please do rebase one more time :)

This revision now requires changes to proceed.Nov 1 2019, 12:02 AM
ali.alzyod updated this revision to Diff 26643.EditedNov 3 2019, 8:03 AM

rebase, disable Height/Width only speedup conditions

This revision was not accepted when it landed; it landed in state Needs Review.Nov 5 2019, 3:49 AM
This revision was automatically updated to reflect the committed changes.

@ali.alzyod

Thanks. It was updated :)

woohyun reopened this revision.Nov 5 2019, 3:51 AM
woohyun accepted this revision.
woohyun added a comment.EditedNov 5 2019, 3:58 AM

@cedric

Could you "accept" this patch to close ? I already updated to the master branch but cannot close because of "none acceptable" state.
(Usage Exception: Revision D9280 can not be closed. You can only close revisions which have been 'accepted'.)

cedric added a comment.Nov 5 2019, 8:46 PM

Sorry, I have forgot about this review. You could have just stored them in a C int array with an additional field named _count. This would have been way better than an Eina_List. There is a lot of example in edje_cc_handlers.c that already use this technic (After a quick scan of the file you can look at line 2356 I think).

This comment was removed by ali.alzyod.
a.srour added a subscriber: a.srour.Nov 6 2019, 2:03 AM

Sorry, I have forgot about this review. You could have just stored them in a C int array with an additional field named _count. This would have been way better than an Eina_List. There is a lot of example in edje_cc_handlers.c that already use this technic (After a quick scan of the file you can look at line 2356 I think).

@cedric So you're suggesting that edc file will be something like this:

...
fit_size: 20;
fit_size: 40;
fit_size: 60;
fit_size: 80;
fit_size: 100;
fit_size: 200;
...

Rather than this?

fit_size_array: 20 40 60 80 100 200;

Right?

  • update name