diff --git a/example/main.c b/example/main.c index d0270ec..fda7e99 100644 --- a/example/main.c +++ b/example/main.c @@ -123,6 +123,18 @@ int main(void) printf("Table:\n%s\n", ft_to_string(table)); ft_destroy_table(table); + + /* Debug */ + ft_set_default_border_style(FT_DOT_STYLE); + table = create_basic_table(); + ft_set_cell_option(table, FT_CUR_ROW, FT_ANY_COLUMN, FT_COPT_ROW_TYPE, FT_ROW_HEADER); + ft_write_ln(table, "Summary", "", "", "8.7"); + ft_some_api(table, 6, 0, 3); + ft_some_api(table, 0, 0, 3); + printf("Table:\n%s\n", ft_to_string(table)); + ft_destroy_table(table); + return 0; + /*-------------------------------------------------------------*/ #if defined(FT_HAVE_WCHAR) && !defined(FT_MICROSOFT_COMPILER) setlocale(LC_CTYPE, ""); diff --git a/include/fort.h b/include/fort.h index 5a0f534..d65fd34 100644 --- a/include/fort.h +++ b/include/fort.h @@ -591,6 +591,9 @@ FT_EXTERN int ft_set_tbl_option(FTABLE *table, uint32_t option, int value); FT_EXTERN void ft_set_memory_funcs(void *(*f_malloc)(size_t size), void (*f_free)(void *ptr)); + +FT_EXTERN void ft_some_api(FTABLE *table, size_t row, size_t col, size_t group_width); + #ifdef FT_HAVE_WCHAR diff --git a/src/cell.c b/src/cell.c index 90fb313..b7133bd 100644 --- a/src/cell.c +++ b/src/cell.c @@ -9,6 +9,7 @@ struct fort_cell { string_buffer_t *str_buffer; + enum CellType cell_type; }; fort_cell_t *create_cell(void) @@ -21,7 +22,7 @@ fort_cell_t *create_cell(void) F_FREE(cell); return NULL; } - /*init_cell_options(&(cell->options));*/ + cell->cell_type = CommonCell; return cell; } @@ -33,6 +34,19 @@ void destroy_cell(fort_cell_t *cell) F_FREE(cell); } +void set_cell_type(fort_cell_t *cell, enum CellType type) +{ + assert(cell); + cell->cell_type = type; +} + +enum CellType get_cell_type(const fort_cell_t *cell) +{ + assert(cell); + return cell->cell_type; +} + + size_t hint_width_cell(const fort_cell_t *cell, const context_t *context) { /* todo: diff --git a/src/cell.h b/src/cell.h index 244715a..42216f6 100644 --- a/src/cell.h +++ b/src/cell.h @@ -17,6 +17,9 @@ size_t hint_width_cell(const fort_cell_t *cell, const context_t *context); size_t hint_height_cell(const fort_cell_t *cell, const context_t *context); +void set_cell_type(fort_cell_t *cell, enum CellType type); +enum CellType get_cell_type(const fort_cell_t *cell); + /* * Returns number of lines in cell. If cell is empty or * contains empty string, then 0 is returned. diff --git a/src/fort.c b/src/fort.c index cb7d215..834d295 100644 --- a/src/fort.c +++ b/src/fort.c @@ -626,7 +626,7 @@ const char *ft_to_string(const FTABLE *table) clear: F_FREE(col_width_arr); F_FREE(row_height_arr); - F_FREE(buffer); +// F_FREE(buffer); return NULL; #undef cur_F_STRDUP } @@ -727,7 +727,7 @@ const wchar_t *ft_to_wstring(const FTABLE *table) clear: F_FREE(col_width_arr); F_FREE(row_height_arr); - F_FREE(buffer); +// F_FREE(buffer); return NULL; #undef cur_F_STRDUP } @@ -937,3 +937,24 @@ FT_EXTERN void ft_set_memory_funcs(void *(*f_malloc)(size_t size), void (*f_free { set_memory_funcs(f_malloc, f_free); } + +#include "cell.h" +FT_EXTERN void ft_some_api(FTABLE *table, size_t row, size_t col, size_t group_width) +{ + assert(table); + if (group_width == 0) + return; + + fort_row_t *row_p = get_row(table, row); + fort_cell_t *main_cell = get_cell(row_p, col); + set_cell_type(main_cell, GroupMasterCell); + --group_width; + ++col; + + while (group_width) { + fort_cell_t *slave_cell = get_cell(row_p, col); + set_cell_type(slave_cell, GroupSlaveCell); + --group_width; + ++col; + } +} diff --git a/src/fort_impl.h b/src/fort_impl.h index 7e1f777..af1583a 100644 --- a/src/fort_impl.h +++ b/src/fort_impl.h @@ -16,6 +16,8 @@ #define FORT_COL_SEPARATOR '|' +#define FORT_COL_SEPARATOR_LENGTH 1 + #define FORT_UNUSED __attribute__((unused)) #define F_MALLOC fort_malloc @@ -102,6 +104,14 @@ typedef struct separator separator_t; +enum CellType +{ + CommonCell, + GroupMasterCell, + GroupSlaveCell +}; + + /***************************************************************************** * LIBFORT helpers *****************************************************************************/ diff --git a/src/row.c b/src/row.c index e09d1ea..515d6a4 100644 --- a/src/row.c +++ b/src/row.c @@ -122,6 +122,41 @@ fort_status_t swap_row(fort_row_t *cur_row, fort_row_t *ins_row, size_t pos) return vector_swap(cur_row->cells, ins_row->cells, pos); } +size_t group_cell_number(const fort_row_t *row, size_t master_cell_col) +{ + assert(row); + const fort_cell_t *cell = get_cell_c(row, master_cell_col); + if (cell == NULL) + return 0; + + size_t total_cols = vector_size(row->cells); + size_t slave_col = master_cell_col + 1; + while (slave_col < total_cols) { + const fort_cell_t *cell = get_cell_c(row, slave_col); + if (cell && get_cell_type(cell) == GroupSlaveCell) { + ++slave_col; + } else { + break; + } + } + return slave_col - master_cell_col; +} + +int get_row_cell_types(const fort_row_t *row, enum CellType *types, size_t types_sz) +{ + assert(row); + assert(types); + size_t i = 0; + for (i = 0; i < types_sz; ++i) { + const fort_cell_t *cell = get_cell_c(row, i); + if (cell) { + types[i] = get_cell_type(cell); + } else { + types[i] = CommonCell; + } + } + return FT_SUCCESS; +} @@ -140,6 +175,34 @@ int print_row_separator(char *buffer, size_t buffer_sz, assert(buffer); assert(context); + int status = -1; + + /* Get cell types + * + * Regions above top row and below bottom row areconsidered full of virtual + * GroupSlaveCell cells + */ + enum CellType *top_row_types = F_MALLOC(sizeof(enum CellType) * cols * 2); + if (top_row_types == NULL) { + return FT_MEMORY_ERROR; + } + enum CellType *bottom_row_types = top_row_types + cols; + if (upper_row) { + get_row_cell_types(upper_row, top_row_types, cols); + } else { + size_t i = 0; + for (i = 0; i < cols; ++i) + top_row_types[i] = GroupSlaveCell; + } + if (lower_row) { + get_row_cell_types(lower_row, bottom_row_types, cols); + } else { + size_t i = 0; + for (i = 0; i < cols; ++i) + bottom_row_types[i] = GroupSlaveCell; + } + + int written = 0; int tmp = 0; @@ -152,15 +215,20 @@ int print_row_separator(char *buffer, size_t buffer_sz, upper_row_type = (enum ft_row_type)get_cell_opt_value_hierarcial(context->table_options, context->row - 1, FT_ANY_COLUMN, FT_COPT_ROW_TYPE); } - - /* Row separator anatomy + /* Row separator anatomy * - * L I I I IV I I I R - */ + * | C11 | C12 C13 | C14 C15 | + * L I I I IV I I IT I I I IB I I II I I R + * | C21 | C22 | C23 C24 C25 | + */ const char *L = NULL; const char *I = NULL; const char *IV = NULL; const char *R = NULL; + const char *IT = NULL; + const char *IB = NULL; + const char *II = NULL; + typedef const char (*border_chars_point_t)[BorderItemPosSize]; const char (*border_chars)[BorderItemPosSize] = NULL; @@ -174,6 +242,10 @@ int print_row_separator(char *buffer, size_t buffer_sz, I = &(context->table_options->border_style.separator_chars[IH_sip]); IV = &(context->table_options->border_style.separator_chars[II_sip]); R = &(context->table_options->border_style.separator_chars[RH_sip]); + + IT = &(context->table_options->border_style.separator_chars[II_sip]); + IB = &(context->table_options->border_style.separator_chars[II_sip]); + II = &(context->table_options->border_style.separator_chars[IH_sip]); } else { switch (separatorPos) { case TopSeparator: @@ -181,18 +253,30 @@ int print_row_separator(char *buffer, size_t buffer_sz, I = &(*border_chars)[TT_bip]; IV = &(*border_chars)[TV_bip]; R = &(*border_chars)[TR_bip]; + + IT = &(*border_chars)[TV_bip]; + IB = &(*border_chars)[TV_bip]; + II = &(*border_chars)[TT_bip]; break; case InsideSeparator: L = &(*border_chars)[LH_bip]; I = &(*border_chars)[IH_bip]; IV = &(*border_chars)[II_bip]; R = &(*border_chars)[RH_bip]; + + IT = &(*border_chars)[TV_bip]; + IB = &(*border_chars)[BV_bip]; + II = &(*border_chars)[IH_bip]; break; case BottomSeparator: L = &(*border_chars)[BL_bip]; I = &(*border_chars)[BB_bip]; IV = &(*border_chars)[BV_bip]; R = &(*border_chars)[BR_bip]; + + IT = &(*border_chars)[BV_bip]; + IB = &(*border_chars)[BV_bip]; + II = &(*border_chars)[BB_bip]; break; default: break; @@ -200,8 +284,10 @@ int print_row_separator(char *buffer, size_t buffer_sz, } /* If all chars are not printable, skip line separator */ /* todo: add processing for wchar_t */ - if (!isprint(*L) && !isprint(*I) && !isprint(*IV) && !isprint(*R)) - return 0; + if (!isprint(*L) && !isprint(*I) && !isprint(*IV) && !isprint(*R)) { + status = 0; + goto clear; + } size_t i = 0; @@ -212,7 +298,16 @@ int print_row_separator(char *buffer, size_t buffer_sz, if (i == 0) { CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buffer_sz - written, 1, (char_type)*L)); } else { - CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buffer_sz - written, 1, (char_type)*IV)); + if ((top_row_types[i] == CommonCell || top_row_types[i] == GroupMasterCell) + && (bottom_row_types[i] == CommonCell || bottom_row_types[i] == GroupMasterCell)) { + CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buffer_sz - written, 1, (char_type)*IV)); + } else if (top_row_types[i] == GroupSlaveCell && bottom_row_types[i] == GroupSlaveCell) { + CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buffer_sz - written, 1, (char_type)*II)); + } else if (top_row_types[i] == GroupSlaveCell) { + CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buffer_sz - written, 1, (char_type)*IT)); + } else { + CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buffer_sz - written, 1, (char_type)*IB)); + } } CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buffer_sz - written, col_width_arr[i], (char_type)*I)); } @@ -223,10 +318,11 @@ int print_row_separator(char *buffer, size_t buffer_sz, CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buffer_sz - written, 1, new_line_char)); - return written; + status = written; clear: - return -1; + F_FREE(top_row_types); + return status; } @@ -665,26 +761,44 @@ int snprintf_row(const fort_row_t *row, char *buffer, size_t buf_sz, size_t *col /* Print left margin */ CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buf_sz - written, context->table_options->entire_table_options.left_margin, space_char)); + /* Print left table boundary */ CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buf_sz - written, 1, (char_type)*L)); size_t j = 0; - for (j = 0; j < col_width_arr_sz; ++j) { - ((context_t *)context)->column = j; + while (j < col_width_arr_sz) { if (j < cols_in_row) { + ((context_t *)context)->column = j; fort_cell_t *cell = *(fort_cell_t **)vector_at(row->cells, j); - CHCK_RSLT_ADD_TO_WRITTEN(cell_printf_(cell, i, buffer + written, col_width_arr[j] + 1, context)); + size_t cell_width = 0; + + size_t group_slave_sz = group_cell_number(row, j); + cell_width = col_width_arr[j]; + size_t slave_j = 0; + size_t master_j = j; + for (slave_j = master_j + 1; slave_j < (master_j + group_slave_sz); ++slave_j) { + cell_width += col_width_arr[slave_j] + FORT_COL_SEPARATOR_LENGTH; + ++j; + } + + CHCK_RSLT_ADD_TO_WRITTEN(cell_printf_(cell, i, buffer + written, cell_width + 1, context)); } else { + /* Print empty cell */ CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buf_sz - written, col_width_arr[j], space_char)); } - if (j == col_width_arr_sz - 1) { - CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buf_sz - written, 1, (char_type)*R)); - } else { + + /* Print boundary between cells */ + if (j < col_width_arr_sz - 1) CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buf_sz - written, 1, (char_type)*IV)); - } + + ++j; } + /* Print right table boundary */ + CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buf_sz - written, 1, (char_type)*R)); + /* Print right margin */ CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buf_sz - written, context->table_options->entire_table_options.right_margin, space_char)); + /* Print new line character */ CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_chars_(buffer + written, buf_sz - written, 1, new_line_char)); } return written; diff --git a/src/row.h b/src/row.h index 6225d22..f3d8d1e 100644 --- a/src/row.h +++ b/src/row.h @@ -27,7 +27,8 @@ const fort_cell_t *get_cell_c(const fort_row_t *row, size_t col); fort_cell_t *get_cell_and_create_if_not_exists(fort_row_t *row, size_t col); fort_status_t swap_row(fort_row_t *cur_row, fort_row_t *ins_row, size_t pos); - +size_t group_cell_number(const fort_row_t *row, size_t master_cell_col); +int get_row_cell_types(const fort_row_t *row, enum CellType *types, size_t types_sz); int print_row_separator(char *buffer, size_t buffer_sz, const size_t *col_width_arr, size_t cols, diff --git a/src/table.c b/src/table.c index ad5b0d4..5a56505 100644 --- a/src/table.c +++ b/src/table.c @@ -112,6 +112,7 @@ fort_status_t table_rows_and_cols_geometry(const FTABLE *table, return FT_ERROR; } + int combined_cells_found = 0; context_t context; context.table_options = (table->options ? table->options : &g_table_options); size_t col = 0; @@ -124,12 +125,56 @@ fort_status_t table_rows_and_cols_geometry(const FTABLE *table, context.column = col; context.row = row; if (cell) { - col_width_arr[col] = MAX(col_width_arr[col], hint_width_cell(cell, &context)); + switch (get_cell_type(cell)) { + case CommonCell: + col_width_arr[col] = MAX(col_width_arr[col], hint_width_cell(cell, &context)); + break; + case GroupMasterCell: + combined_cells_found = 1; + break; + case GroupSlaveCell: + ; /* Do nothing */ + break; + } row_height_arr[row] = MAX(row_height_arr[row], hint_height_cell(cell, &context)); } } } + if (combined_cells_found) { + col = 0; + for (col = 0; col < cols; ++col) { + size_t row = 0; + for (row = 0; row < rows; ++row) { + const fort_row_t *row_p = get_row_c(table, row); + const fort_cell_t *cell = get_cell_c(row_p, col); + context.column = col; + context.row = row; + if (cell) { + if (get_cell_type(cell) == GroupMasterCell) { + size_t hint_width = hint_width_cell(cell, &context); + size_t slave_col = col + group_cell_number(row_p, col); + size_t cur_adj_col = col; + size_t group_width = col_width_arr[col]; + size_t i; + for (i = col + 1; i < slave_col; ++i) + group_width += col_width_arr[i] + FORT_COL_SEPARATOR_LENGTH; + /* adjust col. widths */ + while (1) { + if (group_width >= hint_width) + break; + col_width_arr[cur_adj_col] += 1; + group_width++; + cur_adj_col++; + if (cur_adj_col == slave_col) + cur_adj_col = col; + } + } + } + } + } + } + /* todo: Maybe it is better to move min width checking to a particular cell width checking. * At the moment min width includes paddings. Maybe it is better that min width weren't include * paddings but be min width of the cell content without padding