text: /**
   * @file lv_table.c
   *
   */
  
  /*********************
   *      INCLUDES
   *********************/
  #include "lv_table.h"
  #if LV_USE_TABLE != 0
  
  #include "../lv_misc/lv_debug.h"
  #include "../lv_core/lv_indev.h"
  #include "../lv_misc/lv_txt.h"
  #include "../lv_misc/lv_math.h"
  #include "../lv_draw/lv_draw_label.h"
  #include "../lv_themes/lv_theme.h"
  
  /*********************
   *      DEFINES
   *********************/
  #define LV_OBJX_NAME "lv_table"
  
  /**********************
   *      TYPEDEFS
   **********************/
  
  /**********************
   *  STATIC PROTOTYPES
   **********************/
  static lv_design_res_t lv_table_design(lv_obj_t * table, const lv_area_t * clip_area, lv_design_mode_t mode);
  static lv_res_t lv_table_signal(lv_obj_t * table, lv_signal_t sign, void * param);
  static lv_style_list_t * lv_table_get_style(lv_obj_t * table, uint8_t part);
  static lv_coord_t get_row_height(lv_obj_t * table, uint16_t row_id, const lv_font_t ** font,
                                   lv_style_int_t * letter_space, lv_style_int_t * line_space,
                                   lv_style_int_t * cell_left, lv_style_int_t * cell_right, lv_style_int_t * cell_top, lv_style_int_t * cell_bottom);
  static void refr_size(lv_obj_t * table);
  
  /**********************
   *  STATIC VARIABLES
   **********************/
  static lv_signal_cb_t ancestor_signal;
  static lv_design_cb_t ancestor_design;
  
  /**********************
   *      MACROS
   **********************/
  
  /**********************
   *   GLOBAL FUNCTIONS
   **********************/
  
  /**
   * Create a table object
   * @param par pointer to an object, it will be the parent of the new table
   * @param copy pointer to a table object, if not NULL then the new object will be copied from it
   * @return pointer to the created table
   */
  lv_obj_t * lv_table_create(lv_obj_t * par, const lv_obj_t * copy)
  {
      LV_LOG_TRACE("table create started");
  
      /*Create the ancestor of table*/
      lv_obj_t * table = lv_obj_create(par, copy);
      LV_ASSERT_MEM(table);
      if(table == NULL) return NULL;
  
      /*Allocate the table type specific extended data*/
      lv_table_ext_t * ext = lv_obj_allocate_ext_attr(table, sizeof(lv_table_ext_t));
      LV_ASSERT_MEM(ext);
      if(ext == NULL) {
          lv_obj_del(table);
          return NULL;
      }
  
      if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(table);
      if(ancestor_design == NULL) ancestor_design = lv_obj_get_design_cb(table);
  
      /*Initialize the allocated 'ext' */
      ext->cell_data     = NULL;
      ext->col_cnt       = 0;
      ext->row_cnt       = 0;
      ext->row_h         = NULL;
      ext->cell_types    = 1;
  
      uint16_t i;
      for(i = 0; i < LV_TABLE_CELL_STYLE_CNT; i++) {
          lv_style_list_init(&ext->cell_style[i]);
      }
  
      for(i = 0; i < LV_TABLE_COL_MAX; i++) {
          ext->col_w[i] = LV_DPI;
      }
  
      /*The signal and design functions are not copied so set them here*/
      lv_obj_set_signal_cb(table, lv_table_signal);
      lv_obj_set_design_cb(table, lv_table_design);
  
      /*Init the new table table*/
      if(copy == NULL) {
          lv_theme_apply(table, LV_THEME_TABLE);
      }
      /*Copy an existing table*/
      else {
          lv_table_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
          for(i = 0; i < LV_TABLE_CELL_STYLE_CNT; i++) {
              lv_style_list_copy(&ext->cell_style[i], &copy_ext->cell_style[i]);
              lv_table_set_row_cnt(table, copy_ext->row_cnt);
              lv_table_set_col_cnt(table, copy_ext->col_cnt);
          }
  
          /*Refresh the style with new signal function*/
          lv_obj_refresh_style(table, LV_STYLE_PROP_ALL);
      }
  
      LV_LOG_INFO("table created");
  
      return table;
  }
  
  /*=====================
   * Setter functions
   *====================*/
  
  /**
   * Set the value of a cell.
   * @param table pointer to a Table object
   * @param row id of the row [0 .. row_cnt -1]
   * @param col id of the column [0 .. col_cnt -1]
   * @param txt text to display in the cell. It will be copied and saved so this variable is not
   * required after this function call.
   */
  void lv_table_set_cell_value(lv_obj_t * table, uint16_t row, uint16_t col, const char * txt)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
      LV_ASSERT_NULL(txt);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      if(col >= ext->col_cnt) {
          LV_LOG_WARN("lv_table_set_cell_value: invalid column");
          return;
      }
  
      /*Auto expand*/
      if(row >= ext->row_cnt) {
          lv_table_set_row_cnt(table, row + 1);
      }
  
      uint32_t cell = row * ext->col_cnt + col;
      lv_table_cell_format_t format;
  
      /*Save the format byte*/
      if(ext->cell_data[cell]) {
          format.format_byte = ext->cell_data[cell][0];
      }
      /*Initialize the format byte*/
      else {
          lv_bidi_dir_t base_dir = lv_obj_get_base_dir(table);
          if(base_dir == LV_BIDI_DIR_LTR) format.s.align = LV_LABEL_ALIGN_LEFT;
          else if(base_dir == LV_BIDI_DIR_RTL) format.s.align = LV_LABEL_ALIGN_RIGHT;
          else if(base_dir == LV_BIDI_DIR_AUTO)
  #if LV_USE_BIDI
              format.s.align = _lv_bidi_detect_base_dir(txt);
  #else
              format.s.align = LV_LABEL_ALIGN_LEFT;
  #endif
          format.s.right_merge = 0;
          format.s.type        = 0;
          format.s.crop        = 0;
      }
  
      ext->cell_data[cell] = lv_mem_realloc(ext->cell_data[cell], strlen(txt) + 2); /*+1: trailing '\0; +1: format byte*/
      LV_ASSERT_MEM(ext->cell_data[cell]);
      if(ext->cell_data[cell] == NULL) return;
  
      strcpy(ext->cell_data[cell] + 1, txt);  /*+1 to skip the format byte*/
  
      ext->cell_data[cell][0] = format.format_byte;
      refr_size(table);
  }
  
  /**
   * Set the number of rows
   * @param table table pointer to a Table object
   * @param row_cnt number of rows
   */
  void lv_table_set_row_cnt(lv_obj_t * table, uint16_t row_cnt)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      uint16_t old_row_cnt = ext->row_cnt;
      ext->row_cnt         = row_cnt;
  
      if(ext->row_cnt > 0 && ext->col_cnt > 0) {
          ext->cell_data = lv_mem_realloc(ext->cell_data, ext->row_cnt * ext->col_cnt * sizeof(char *));
          LV_ASSERT_MEM(ext->cell_data);
          if(ext->cell_data == NULL) return;
  
          /*Initilize the new fields*/
          if(old_row_cnt < row_cnt) {
              uint16_t old_cell_cnt = old_row_cnt * ext->col_cnt;
              uint32_t new_cell_cnt = ext->col_cnt * ext->row_cnt;
              _lv_memset_00(&ext->cell_data[old_cell_cnt], (new_cell_cnt - old_cell_cnt) * sizeof(ext->cell_data[0]));
          }
  
          ext->row_h = lv_mem_realloc(ext->row_h, ext->row_cnt * sizeof(ext->row_h[0]));
          LV_ASSERT_MEM(ext->cell_data);
          if(ext->cell_data == NULL) return;
      }
      else {
          lv_mem_free(ext->cell_data);
          ext->cell_data = NULL;
      }
  
      refr_size(table);
  }
  
  /**
   * Set the number of columns
   * @param table table pointer to a Table object
   * @param col_cnt number of columns. Must be < LV_TABLE_COL_MAX
   */
  void lv_table_set_col_cnt(lv_obj_t * table, uint16_t col_cnt)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      if(col_cnt >= LV_TABLE_COL_MAX) {
          LV_LOG_WARN("lv_table_set_col_cnt: too many columns. Must be < LV_TABLE_COL_MAX.");
          return;
      }
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      uint16_t old_col_cnt = ext->col_cnt;
      ext->col_cnt         = col_cnt;
  
      if(ext->row_cnt > 0 && ext->col_cnt > 0) {
          ext->cell_data = lv_mem_realloc(ext->cell_data, ext->row_cnt * ext->col_cnt * sizeof(char *));
          LV_ASSERT_MEM(ext->cell_data);
          if(ext->cell_data == NULL) return;
  
          /*Initilize the new fields*/
          if(old_col_cnt < col_cnt) {
              uint16_t old_cell_cnt = old_col_cnt * ext->row_cnt;
              uint32_t new_cell_cnt = ext->col_cnt * ext->row_cnt;
              _lv_memset_00(&ext->cell_data[old_cell_cnt], (new_cell_cnt - old_cell_cnt) * sizeof(ext->cell_data[0]));
          }
  
      }
      else {
          lv_mem_free(ext->cell_data);
          ext->cell_data = NULL;
      }
      refr_size(table);
  }
  
  /**
   * Set the width of a column
   * @param table table pointer to a Table object
   * @param col_id id of the column [0 .. LV_TABLE_COL_MAX -1]
   * @param w width of the column
   */
  void lv_table_set_col_width(lv_obj_t * table, uint16_t col_id, lv_coord_t w)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      if(col_id >= LV_TABLE_COL_MAX) {
          LV_LOG_WARN("lv_table_set_col_width: too big 'col_id'. Must be < LV_TABLE_COL_MAX.");
          return;
      }
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      ext->col_w[col_id]   = w;
      refr_size(table);
  }
  
  /**
   * Set the text align in a cell
   * @param table pointer to a Table object
   * @param row id of the row [0 .. row_cnt -1]
   * @param col id of the column [0 .. col_cnt -1]
   * @param align LV_LABEL_ALIGN_LEFT or LV_LABEL_ALIGN_CENTER or LV_LABEL_ALIGN_RIGHT
   */
  void lv_table_set_cell_align(lv_obj_t * table, uint16_t row, uint16_t col, lv_label_align_t align)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      if(col >= ext->col_cnt) {
          LV_LOG_WARN("lv_table_set_cell_align: invalid column");
          return;
      }
  
      /*Auto expand*/
      if(row >= ext->row_cnt) {
          lv_table_set_row_cnt(table, row + 1);
      }
      uint32_t cell = row * ext->col_cnt + col;
  
      if(ext->cell_data[cell] == NULL) {
          ext->cell_data[cell]    = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/
          LV_ASSERT_MEM(ext->cell_data[cell]);
          if(ext->cell_data[cell] == NULL) return;
  
          ext->cell_data[cell][0] = 0;
          ext->cell_data[cell][1] = '\0';
      }
  
      lv_table_cell_format_t format;
      format.format_byte      = ext->cell_data[cell][0];
      format.s.align          = align;
      ext->cell_data[cell][0] = format.format_byte;
  }
  
  /**
   * Set the type of a cell.
   * @param table pointer to a Table object
   * @param row id of the row [0 .. row_cnt -1]
   * @param col id of the column [0 .. col_cnt -1]
   * @param type 1,2,3 or 4. The cell style will be chosen accordingly.
   */
  void lv_table_set_cell_type(lv_obj_t * table, uint16_t row, uint16_t col, uint8_t type)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      if(col >= ext->col_cnt) {
          LV_LOG_WARN("lv_table_set_cell_type: invalid column");
          return;
      }
  
      /*Auto expand*/
      if(row >= ext->row_cnt) {
          lv_table_set_row_cnt(table, row + 1);
      }
  
      uint32_t cell = row * ext->col_cnt + col;
  
      if(ext->cell_data[cell] == NULL) {
          ext->cell_data[cell]    = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/
          LV_ASSERT_MEM(ext->cell_data[cell]);
          if(ext->cell_data[cell] == NULL) return;
  
          ext->cell_data[cell][0] = 0;
          ext->cell_data[cell][1] = '\0';
      }
  
      if(type > 0) type--; /*User gives 1,2,3,4 but easier to handle 0, 1, 2, 3*/
      if(type >= LV_TABLE_CELL_STYLE_CNT) type = LV_TABLE_CELL_STYLE_CNT - 1;
  
      lv_table_cell_format_t format;
      format.format_byte      = ext->cell_data[cell][0];
      format.s.type           = type;
      ext->cell_data[cell][0] = format.format_byte;
  
      ext->cell_types |= 1 << type;
  }
  
  /**
   * Set the cell crop. (Don't adjust the height of the cell according to its content)
   * @param table pointer to a Table object
   * @param row id of the row [0 .. row_cnt -1]
   * @param col id of the column [0 .. col_cnt -1]
   * @param crop true: crop the cell content; false: set the cell height to the content.
   */
  void lv_table_set_cell_crop(lv_obj_t * table, uint16_t row, uint16_t col, bool crop)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      if(col >= ext->col_cnt) {
          LV_LOG_WARN("lv_table_set_cell_crop: invalid column");
          return;
      }
  
      /*Auto expand*/
      if(row >= ext->row_cnt) {
          lv_table_set_row_cnt(table, row + 1);
      }
  
      uint32_t cell = row * ext->col_cnt + col;
  
      if(ext->cell_data[cell] == NULL) {
          ext->cell_data[cell]    = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/
          LV_ASSERT_MEM(ext->cell_data[cell]);
          if(ext->cell_data[cell] == NULL) return;
  
          ext->cell_data[cell][0] = 0;
          ext->cell_data[cell][1] = '\0';
      }
  
      lv_table_cell_format_t format;
      format.format_byte      = ext->cell_data[cell][0];
      format.s.crop           = crop;
      ext->cell_data[cell][0] = format.format_byte;
  }
  
  /**
   * Merge a cell with the right neighbor. The value of the cell to the right won't be displayed.
   * @param table table pointer to a Table object
   * @param row id of the row [0 .. row_cnt -1]
   * @param col id of the column [0 .. col_cnt -1]
   * @param en true: merge right; false: don't merge right
   */
  void lv_table_set_cell_merge_right(lv_obj_t * table, uint16_t row, uint16_t col, bool en)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      if(col >= ext->col_cnt) {
          LV_LOG_WARN("lv_table_set_cell_merge_right: invalid column");
          return;
      }
  
      /*Auto expand*/
      if(row >= ext->row_cnt) {
          lv_table_set_row_cnt(table, row + 1);
      }
  
      uint32_t cell = row * ext->col_cnt + col;
  
      if(ext->cell_data[cell] == NULL) {
          ext->cell_data[cell]    = lv_mem_alloc(2); /*+1: trailing '\0; +1: format byte*/
          LV_ASSERT_MEM(ext->cell_data[cell]);
          if(ext->cell_data[cell] == NULL) return;
  
          ext->cell_data[cell][0] = 0;
          ext->cell_data[cell][1] = '\0';
      }
  
      lv_table_cell_format_t format;
      format.format_byte      = ext->cell_data[cell][0];
      format.s.right_merge    = en ? 1 : 0;
      ext->cell_data[cell][0] = format.format_byte;
      refr_size(table);
  }
  
  /*=====================
   * Getter functions
   *====================*/
  
  /**
   * Get the value of a cell.
   * @param table pointer to a Table object
   * @param row id of the row [0 .. row_cnt -1]
   * @param col id of the column [0 .. col_cnt -1]
   * @return text in the cell
   */
  const char * lv_table_get_cell_value(lv_obj_t * table, uint16_t row, uint16_t col)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      if(row >= ext->row_cnt || col >= ext->col_cnt) {
          LV_LOG_WARN("lv_table_set_cell_value: invalid row or column");
          return "";
      }
      uint32_t cell = row * ext->col_cnt + col;
  
      if(ext->cell_data[cell] == NULL) return "";
  
      return &ext->cell_data[cell][1]; /*Skip the format byte*/
  }
  
  /**
   * Get the number of rows.
   * @param table table pointer to a Table object
   * @return number of rows.
   */
  uint16_t lv_table_get_row_cnt(lv_obj_t * table)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      return ext->row_cnt;
  }
  
  /**
   * Get the number of columns.
   * @param table table pointer to a Table object
   * @return number of columns.
   */
  uint16_t lv_table_get_col_cnt(lv_obj_t * table)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      return ext->col_cnt;
  }
  
  /**
   * Get the width of a column
   * @param table table pointer to a Table object
   * @param col_id id of the column [0 .. LV_TABLE_COL_MAX -1]
   * @return width of the column
   */
  lv_coord_t lv_table_get_col_width(lv_obj_t * table, uint16_t col_id)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      if(col_id >= LV_TABLE_COL_MAX) {
          LV_LOG_WARN("lv_table_set_col_width: too big 'col_id'. Must be < LV_TABLE_COL_MAX.");
          return 0;
      }
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      return ext->col_w[col_id];
  }
  
  /**
   * Get the text align of a cell
   * @param table pointer to a Table object
   * @param row id of the row [0 .. row_cnt -1]
   * @param col id of the column [0 .. col_cnt -1]
   * @return LV_LABEL_ALIGN_LEFT (default in case of error) or LV_LABEL_ALIGN_CENTER or
   * LV_LABEL_ALIGN_RIGHT
   */
  lv_label_align_t lv_table_get_cell_align(lv_obj_t * table, uint16_t row, uint16_t col)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      if(row >= ext->row_cnt || col >= ext->col_cnt) {
          LV_LOG_WARN("lv_table_set_cell_align: invalid row or column");
          return LV_LABEL_ALIGN_LEFT; /*Just return with something*/
      }
      uint32_t cell = row * ext->col_cnt + col;
  
      if(ext->cell_data[cell] == NULL)
          return LV_LABEL_ALIGN_LEFT; /*Just return with something*/
      else {
          lv_table_cell_format_t format;
          format.format_byte = ext->cell_data[cell][0];
          return format.s.align;
      }
  }
  
  /**
   * Get the type of a cell
   * @param table pointer to a Table object
   * @param row id of the row [0 .. row_cnt -1]
   * @param col id of the column [0 .. col_cnt -1]
   * @return 1,2,3 or 4
   */
  lv_label_align_t lv_table_get_cell_type(lv_obj_t * table, uint16_t row, uint16_t col)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      if(row >= ext->row_cnt || col >= ext->col_cnt) {
          LV_LOG_WARN("lv_table_get_cell_type: invalid row or column");
          return 1; /*Just return with something*/
      }
      uint32_t cell = row * ext->col_cnt + col;
  
      if(ext->cell_data[cell] == NULL)
          return 1; /*Just return with something*/
      else {
          lv_table_cell_format_t format;
          format.format_byte = ext->cell_data[cell][0];
          return format.s.type + 1; /*0,1,2,3 is stored but user sees 1,2,3,4*/
      }
  }
  
  /**
   * Get the crop property of a cell
   * @param table pointer to a Table object
   * @param row id of the row [0 .. row_cnt -1]
   * @param col id of the column [0 .. col_cnt -1]
   * @return true: text crop enabled; false: disabled
   */
  lv_label_align_t lv_table_get_cell_crop(lv_obj_t * table, uint16_t row, uint16_t col)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      if(row >= ext->row_cnt || col >= ext->col_cnt) {
          LV_LOG_WARN("lv_table_get_cell_crop: invalid row or column");
          return false; /*Just return with something*/
      }
      uint32_t cell = row * ext->col_cnt + col;
  
      if(ext->cell_data[cell] == NULL)
          return false; /*Just return with something*/
      else {
          lv_table_cell_format_t format;
          format.format_byte = ext->cell_data[cell][0];
          return format.s.crop;
      }
  }
  
  /**
   * Get the cell merge attribute.
   * @param table table pointer to a Table object
   * @param row id of the row [0 .. row_cnt -1]
   * @param col id of the column [0 .. col_cnt -1]
   * @return true: merge right; false: don't merge right
   */
  bool lv_table_get_cell_merge_right(lv_obj_t * table, uint16_t row, uint16_t col)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      if(row >= ext->row_cnt || col >= ext->col_cnt) {
          LV_LOG_WARN("lv_table_get_cell_merge_right: invalid row or column");
          return false;
      }
  
      uint32_t cell = row * ext->col_cnt + col;
  
      if(ext->cell_data[cell] == NULL)
          return false;
      else {
          lv_table_cell_format_t format;
          format.format_byte = ext->cell_data[cell][0];
          return format.s.right_merge ? true : false;
      }
  }
  
  /**
   * Get the last pressed or being pressed cell
   * @param table pointer to a table object
   * @param row pointer to variable to store the pressed row
   * @param col pointer to variable to store the pressed column
   * @return LV_RES_OK: a valid pressed cell was found, LV_RES_INV: no valid cell is pressed
   */
  lv_res_t lv_table_get_pressed_cell(lv_obj_t * table, uint16_t * row, uint16_t * col)
  {
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
  
      lv_indev_type_t type = lv_indev_get_type(lv_indev_get_act());
      if(type != LV_INDEV_TYPE_POINTER && type != LV_INDEV_TYPE_BUTTON) {
          if(col) *col = 0xFFFF;
          if(row) *row = 0xFFFF;
          return LV_RES_INV;
      }
  
      lv_point_t p;
      lv_indev_get_point(lv_indev_get_act(), &p);
  
      lv_coord_t tmp;
      if(col) {
          lv_coord_t x = p.x;
          x -= table->coords.x1;
          x -= lv_obj_get_style_pad_left(table, LV_TABLE_PART_BG);
          *col = 0;
          tmp = 0;
          for(*col = 0; *col < ext->col_cnt; (*col)++) {
              tmp += ext->col_w[*col];
              if(x < tmp) break;
          }
      }
  
      if(row) {
          lv_coord_t y = p.y;
          y -= table->coords.y1;
          y -= lv_obj_get_style_pad_top(table, LV_TABLE_PART_BG);
  
          *row = 0;
          tmp = 0;
  
          for(*row = 0; *row < ext->row_cnt; (*row)++) {
              tmp += ext->row_h[*row];
              if(y < tmp) break;
          }
      }
  
      return LV_RES_OK;
  }
  
  /**********************
   *   STATIC FUNCTIONS
   **********************/
  
  /**
   * Handle the drawing related tasks of the tables
   * @param table pointer to an object
   * @param clip_area the object will be drawn only in this area
   * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
   *                                  (return 'true' if yes)
   *             LV_DESIGN_DRAW: draw the object (always return 'true')
   *             LV_DESIGN_DRAW_POST: drawing after every children are drawn
   * @param return an element of `lv_design_res_t`
   */
  static lv_design_res_t lv_table_design(lv_obj_t * table, const lv_area_t * clip_area, lv_design_mode_t mode)
  {
      /*Return false if the object is not covers the mask_p area*/
      if(mode == LV_DESIGN_COVER_CHK) {
          return ancestor_design(table, clip_area, mode);
      }
      /*Draw the object*/
      else if(mode == LV_DESIGN_DRAW_MAIN) {
          /*Draw the background*/
          ancestor_design(table, clip_area, mode);
  
          lv_table_ext_t * ext        = lv_obj_get_ext_attr(table);
  
          lv_point_t txt_size;
          lv_area_t cell_area;
          lv_area_t txt_area;
          lv_txt_flag_t txt_flags;
  
          lv_style_int_t bg_top = lv_obj_get_style_pad_top(table, LV_TABLE_PART_BG);
          lv_style_int_t bg_bottom = lv_obj_get_style_pad_bottom(table, LV_TABLE_PART_BG);
          lv_style_int_t bg_left = lv_obj_get_style_pad_left(table, LV_TABLE_PART_BG);
          lv_style_int_t bg_right = lv_obj_get_style_pad_right(table, LV_TABLE_PART_BG);
  
          lv_draw_rect_dsc_t rect_dsc[LV_TABLE_CELL_STYLE_CNT];
          lv_draw_label_dsc_t label_dsc[LV_TABLE_CELL_STYLE_CNT];
          lv_draw_line_dsc_t line_dsc[LV_TABLE_CELL_STYLE_CNT];
          lv_style_int_t cell_left[LV_TABLE_CELL_STYLE_CNT];
          lv_style_int_t cell_right[LV_TABLE_CELL_STYLE_CNT];
          lv_style_int_t cell_top[LV_TABLE_CELL_STYLE_CNT];
          lv_style_int_t cell_bottom[LV_TABLE_CELL_STYLE_CNT];
  
          uint16_t i;
          for(i = 0; i < LV_TABLE_CELL_STYLE_CNT; i++) {
              if((ext->cell_types & (1 << i)) == 0) continue; /*Skip unused cell types*/
              lv_draw_rect_dsc_init(&rect_dsc[i]);
              lv_obj_init_draw_rect_dsc(table, LV_TABLE_PART_CELL1 + i, &rect_dsc[i]);
  
              lv_draw_label_dsc_init(&label_dsc[i]);
              lv_obj_init_draw_label_dsc(table, LV_TABLE_PART_CELL1 + i, &label_dsc[i]);
  
              lv_draw_line_dsc_init(&line_dsc[i]);
              lv_obj_init_draw_line_dsc(table, LV_TABLE_PART_CELL1 + i, &line_dsc[i]);
  
              cell_left[i] = lv_obj_get_style_pad_left(table, LV_TABLE_PART_CELL1 + i);
              cell_right[i] = lv_obj_get_style_pad_right(table, LV_TABLE_PART_CELL1 + i);
              cell_top[i] = lv_obj_get_style_pad_top(table, LV_TABLE_PART_CELL1 + i);
              cell_bottom[i] = lv_obj_get_style_pad_bottom(table, LV_TABLE_PART_CELL1 + i);
          }
  
          uint16_t col;
          uint16_t row;
          uint16_t cell = 0;
  
          cell_area.y2 = table->coords.y1 + bg_top - 1;
          for(row = 0; row < ext->row_cnt; row++) {
              lv_coord_t h_row = ext->row_h[row];
  
              cell_area.y1 = cell_area.y2 + 1;
              cell_area.y2 = cell_area.y1 + h_row - 1;
  
              if(cell_area.y1 > clip_area->y2) return LV_DESIGN_RES_OK;
  
              cell_area.x2 = table->coords.x1 + bg_left - 1;
  
              for(col = 0; col < ext->col_cnt; col++) {
  
                  lv_table_cell_format_t format;
                  if(ext->cell_data[cell]) {
                      format.format_byte = ext->cell_data[cell][0];
                  }
                  else {
                      format.s.right_merge = 0;
                      format.s.align       = LV_LABEL_ALIGN_LEFT;
                      format.s.type        = 0;
                      format.s.crop        = 1;
                  }
  
                  cell_area.x1 = cell_area.x2 + 1;
                  cell_area.x2 = cell_area.x1 + ext->col_w[col] - 1;
  
                  uint16_t col_merge = 0;
                  for(col_merge = 0; col_merge + col < ext->col_cnt - 1; col_merge++) {
                      if(ext->cell_data[cell + col_merge] != NULL) {
                          format.format_byte = ext->cell_data[cell + col_merge][0];
                          if(format.s.right_merge)
                              cell_area.x2 += ext->col_w[col + col_merge + 1];
                          else
                              break;
                      }
                      else {
                          break;
                      }
                  }
  
                  if(cell_area.y2 < clip_area->y1) {
                      cell += col_merge + 1;
                      col += col_merge;
                      continue;
                  }
  
                  uint8_t cell_type = format.s.type;
  
                  /*Expand the cell area with a half border to avoid drawing 2 borders next to each other*/
                  lv_area_t cell_area_border;
                  lv_area_copy(&cell_area_border, &cell_area);
                  if((rect_dsc[cell_type].border_side & LV_BORDER_SIDE_LEFT) && cell_area_border.x1 > table->coords.x1 + bg_left) {
                      cell_area_border.x1 -= rect_dsc[cell_type].border_width / 2;
                  }
                  if((rect_dsc[cell_type].border_side & LV_BORDER_SIDE_TOP) && cell_area_border.y1 > table->coords.y1 + bg_top) {
                      cell_area_border.y1 -= rect_dsc[cell_type].border_width / 2;
                  }
                  if((rect_dsc[cell_type].border_side & LV_BORDER_SIDE_RIGHT) && cell_area_border.x2 < table->coords.x2 - bg_right - 1) {
                      cell_area_border.x2 += rect_dsc[cell_type].border_width / 2 + (rect_dsc[cell_type].border_width & 0x1);
                  }
                  if((rect_dsc[cell_type].border_side & LV_BORDER_SIDE_BOTTOM) &&
                     cell_area_border.y2 < table->coords.y2 - bg_bottom - 1) {
                      cell_area_border.y2 += rect_dsc[cell_type].border_width / 2 + (rect_dsc[cell_type].border_width & 0x1);
                  }
  
                  lv_draw_rect(&cell_area_border, clip_area, &rect_dsc[cell_type]);
  
                  if(ext->cell_data[cell]) {
                      txt_area.x1 = cell_area.x1 + cell_left[cell_type];
                      txt_area.x2 = cell_area.x2 - cell_right[cell_type];
                      txt_area.y1 = cell_area.y1 + cell_top[cell_type];
                      txt_area.y2 = cell_area.y2 - cell_bottom[cell_type];
  
                      /*Align the content to the middle if not cropped*/
                      if(format.s.crop == 0) {
                          txt_flags = LV_TXT_FLAG_NONE;
                      }
                      else {
                          txt_flags = LV_TXT_FLAG_EXPAND;
                      }
  
                      _lv_txt_get_size(&txt_size, ext->cell_data[cell] + 1, label_dsc[cell_type].font,
                                       label_dsc[cell_type].letter_space, label_dsc[cell_type].line_space,
                                       lv_area_get_width(&txt_area), txt_flags);
  
                      label_dsc[cell_type].flag = 0;
                      /*Align the content to the middle if not cropped*/
                      if(format.s.crop == 0) {
                          txt_area.y1 = cell_area.y1 + h_row / 2 - txt_size.y / 2;
                          txt_area.y2 = cell_area.y1 + h_row / 2 + txt_size.y / 2;
                      }
  
                      switch(format.s.align) {
                          default:
                          case LV_LABEL_ALIGN_LEFT:
                              label_dsc[cell_type].flag |= LV_TXT_FLAG_NONE;
                              break;
                          case LV_LABEL_ALIGN_RIGHT:
                              label_dsc[cell_type].flag |= LV_TXT_FLAG_RIGHT;
                              break;
                          case LV_LABEL_ALIGN_CENTER:
                              label_dsc[cell_type].flag |= LV_TXT_FLAG_CENTER;
                              break;
                      }
  
                      lv_area_t label_mask;
                      bool label_mask_ok;
                      label_mask_ok = _lv_area_intersect(&label_mask, clip_area, &cell_area);
                      if(label_mask_ok) {
                          lv_draw_label(&txt_area, &label_mask, &label_dsc[cell_type], ext->cell_data[cell] + 1, NULL);
                      }
  
                      /*Draw lines after '\n's*/
                      lv_point_t p1;
                      lv_point_t p2;
                      p1.x = cell_area.x1;
                      p2.x = cell_area.x2;
                      for(i = 1; ext->cell_data[cell][i] != '\0'; i++) {
                          if(ext->cell_data[cell][i] == '\n') {
                              ext->cell_data[cell][i] = '\0';
                              _lv_txt_get_size(&txt_size, ext->cell_data[cell] + 1, label_dsc[cell_type].font,
                                               label_dsc[cell_type].letter_space, label_dsc[cell_type].line_space,
                                               lv_area_get_width(&txt_area), txt_flags);
  
                              p1.y = txt_area.y1 + txt_size.y + label_dsc[cell_type].line_space / 2;
                              p2.y = txt_area.y1 + txt_size.y + label_dsc[cell_type].line_space / 2;
                              lv_draw_line(&p1, &p2, clip_area, &line_dsc[cell_type]);
  
                              ext->cell_data[cell][i] = '\n';
                          }
                      }
                  }
  
                  cell += col_merge + 1;
                  col += col_merge;
              }
          }
      }
      /*Post draw when the children are drawn*/
      else if(mode == LV_DESIGN_DRAW_POST) {
          ancestor_design(table, clip_area, mode);
      }
  
      return LV_DESIGN_RES_OK;
  }
  
  /**
   * Signal function of the table
   * @param table pointer to a table object
   * @param sign a signal type from lv_signal_t enum
   * @param param pointer to a signal specific variable
   * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
   */
  static lv_res_t lv_table_signal(lv_obj_t * table, lv_signal_t sign, void * param)
  {
      lv_res_t res;
      if(sign == LV_SIGNAL_GET_STYLE) {
          lv_get_style_info_t * info = param;
          info->result = lv_table_get_style(table, info->part);
          if(info->result != NULL) return LV_RES_OK;
          else return ancestor_signal(table, sign, param);
      }
  
      /* Include the ancient signal function */
      res = ancestor_signal(table, sign, param);
      if(res != LV_RES_OK) return res;
      if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
  
      if(sign == LV_SIGNAL_CLEANUP) {
          /*Free the cell texts*/
          lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
          uint16_t i;
          for(i = 0; i < ext->col_cnt * ext->row_cnt; i++) {
              if(ext->cell_data[i]) {
                  lv_mem_free(ext->cell_data[i]);
                  ext->cell_data[i] = NULL;
              }
          }
  
          if(ext->cell_data) lv_mem_free(ext->cell_data);
          if(ext->row_h) lv_mem_free(ext->row_h);
  
          for(i = 0; i < LV_TABLE_CELL_STYLE_CNT; i++) {
              lv_obj_clean_style_list(table, LV_TABLE_PART_CELL1 + i);
          }
      }
      else if(sign == LV_SIGNAL_STYLE_CHG) {
          refr_size(table);
      }
  
      return res;
  }
  
  
  /**
   * Get the style descriptor of a part of the object
   * @param table pointer the object
   * @param part the part from. (LV_TABLE_PART_...)
   * @return pointer to the style descriptor of the specified part
   */
  static lv_style_list_t * lv_table_get_style(lv_obj_t * table, uint8_t part)
  {
      LV_ASSERT_OBJ(table, LV_OBJX_NAME);
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      lv_style_list_t * style_dsc_p;
  
      switch(part) {
          case LV_TABLE_PART_BG:
              style_dsc_p = &table->style_list;
              break;
          case LV_TABLE_PART_CELL1:
              style_dsc_p = &ext->cell_style[0];
              break;
          case LV_TABLE_PART_CELL2:
              style_dsc_p = &ext->cell_style[1];
              break;
          case LV_TABLE_PART_CELL3:
              style_dsc_p = &ext->cell_style[2];
              break;
          case LV_TABLE_PART_CELL4:
              style_dsc_p = &ext->cell_style[3];
              break;
          default:
              style_dsc_p = NULL;
      }
  
      return style_dsc_p;
  }
  
  static void refr_size(lv_obj_t * table)
  {
      lv_coord_t h = 0;
      lv_coord_t w = 0;
  
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
  
      uint16_t i;
      for(i = 0; i < ext->col_cnt; i++) {
          w += ext->col_w[i];
      }
  
      lv_style_int_t cell_left[LV_TABLE_CELL_STYLE_CNT];
      lv_style_int_t cell_right[LV_TABLE_CELL_STYLE_CNT];
      lv_style_int_t cell_top[LV_TABLE_CELL_STYLE_CNT];
      lv_style_int_t cell_bottom[LV_TABLE_CELL_STYLE_CNT];
      lv_style_int_t letter_space[LV_TABLE_CELL_STYLE_CNT];
      lv_style_int_t line_space[LV_TABLE_CELL_STYLE_CNT];
      const lv_font_t * font[LV_TABLE_CELL_STYLE_CNT];
  
      for(i = 0; i < LV_TABLE_CELL_STYLE_CNT; i++) {
          if((ext->cell_types & (1 << i)) == 0) continue; /*Skip unused cell types*/
          cell_left[i] = lv_obj_get_style_pad_left(table, LV_TABLE_PART_CELL1 + i);
          cell_right[i] = lv_obj_get_style_pad_right(table, LV_TABLE_PART_CELL1 + i);
          cell_top[i] = lv_obj_get_style_pad_top(table, LV_TABLE_PART_CELL1 + i);
          cell_bottom[i] = lv_obj_get_style_pad_bottom(table, LV_TABLE_PART_CELL1 + i);
          letter_space[i] = lv_obj_get_style_text_letter_space(table, LV_TABLE_PART_CELL1 + i);
          line_space[i] = lv_obj_get_style_text_line_space(table, LV_TABLE_PART_CELL1 + i);
          font[i] = lv_obj_get_style_text_font(table, LV_TABLE_PART_CELL1 + i);
      }
  
  
      for(i = 0; i < ext->row_cnt; i++) {
          ext->row_h[i] = get_row_height(table, i, font, letter_space, line_space,
                                         cell_left, cell_right, cell_top, cell_bottom);
          h += ext->row_h[i];
      }
  
      lv_style_int_t bg_top = lv_obj_get_style_pad_top(table, LV_TABLE_PART_BG);
      lv_style_int_t bg_bottom = lv_obj_get_style_pad_bottom(table, LV_TABLE_PART_BG);
      lv_style_int_t bg_left = lv_obj_get_style_pad_left(table, LV_TABLE_PART_BG);
      lv_style_int_t bg_right = lv_obj_get_style_pad_right(table, LV_TABLE_PART_BG);
      w += bg_left + bg_right;
      h += bg_top + bg_bottom;
  
      lv_obj_set_size(table, w + 1, h + 1);
      lv_obj_invalidate(table); /*Always invalidate even if the size hasn't changed*/
  }
  
  static lv_coord_t get_row_height(lv_obj_t * table, uint16_t row_id, const lv_font_t ** font,
                                   lv_style_int_t * letter_space, lv_style_int_t * line_space,
                                   lv_style_int_t * cell_left, lv_style_int_t * cell_right, lv_style_int_t * cell_top, lv_style_int_t * cell_bottom)
  {
      lv_table_ext_t * ext = lv_obj_get_ext_attr(table);
      lv_point_t txt_size;
      lv_coord_t txt_w;
  
      uint16_t row_start = row_id * ext->col_cnt;
      uint16_t cell;
      uint16_t col;
      lv_coord_t h_max = lv_font_get_line_height(font[0]) + cell_top[0] + cell_bottom[0];
  
      for(cell = row_start, col = 0; cell < row_start + ext->col_cnt; cell++, col++) {
          if(ext->cell_data[cell] != NULL) {
              txt_w              = ext->col_w[col];
              uint16_t col_merge = 0;
              for(col_merge = 0; col_merge + col < ext->col_cnt - 1; col_merge++) {
  
                  if(ext->cell_data[cell + col_merge] != NULL) {
                      lv_table_cell_format_t format;
                      format.format_byte = ext->cell_data[cell + col_merge][0];
                      if(format.s.right_merge)
                          txt_w += ext->col_w[col + col_merge + 1];
                      else
                          break;
                  }
                  else {
                      break;
                  }
              }
  
              lv_table_cell_format_t format;
              format.format_byte = ext->cell_data[cell][0];
              uint8_t cell_type  = format.s.type;
  
              /*With text crop assume 1 line*/
              if(format.s.crop) {
                  h_max = LV_MATH_MAX(lv_font_get_line_height(font[cell_type]) + cell_top[cell_type] + cell_bottom[cell_type],
                                      h_max);
              }
              /*Without text crop calculate the height of the text in the cell*/
              else {
                  txt_w -= cell_left[cell_type] + cell_right[cell_type];
  
                  _lv_txt_get_size(&txt_size, ext->cell_data[cell] + 1, font[cell_type],
                                   letter_space[cell_type], line_space[cell_type], txt_w, LV_TXT_FLAG_NONE);
  
                  h_max = LV_MATH_MAX(txt_size.y + cell_top[cell_type] + cell_bottom[cell_type], h_max);
                  cell += col_merge;
                  col += col_merge;
              }
          }
      }
  
      return h_max;
  }
  
  #endif
  