diff --git a/lib/fort.c b/lib/fort.c index 91f81bd..3c7ecee 100644 --- a/lib/fort.c +++ b/lib/fort.c @@ -486,6 +486,12 @@ utf8_nonnull utf8_pure utf8_weak size_t utf8len(const void *str); // Visible width of utf8string. utf8_nonnull utf8_pure utf8_weak size_t utf8width(const void *str); +// Forward pointer by `vis_w` visible codepoints +// (or less if end of string is encountered). Sets `width` to visible width of +// string between `str` and returned value. +utf8_nonnull utf8_pure utf8_weak const void *utf8forw(const void *str, + size_t vis_w, size_t *width); + // Visible width of codepoint. utf8_nonnull utf8_pure utf8_weak int utf8cwidth(utf8_int32_t c); @@ -909,6 +915,36 @@ size_t utf8width(const void *str) return length; } +const void *utf8forw(const void *str, size_t vis_w, size_t *width) +{ + size_t length = 0; + utf8_int32_t c = 0; + /* We need to store previous value of str + * because some codepoints may occupy more than + * one symbol and during iteration we may exceed vis_w. + */ + const void *prev_str = str; + size_t old_length = 0; + + str = utf8codepoint(str, &c); + while (c != 0) { + old_length = length; + length += utf8cwidth(c); + if (length > vis_w) + break; + prev_str = str; + str = utf8codepoint(str, &c); + } + + if (length > vis_w) { + *width = old_length; + return prev_str; + } else { + *width = length; + return str; + } +} + int utf8ncasecmp(const void *src1, const void *src2, size_t n) { utf8_int32_t src1_cp, src2_cp, src1_orig_cp, src2_orig_cp; @@ -1853,6 +1889,7 @@ void buffer_set_u8strwid_func(int (*u8strwid)(const void *beg, const void *end, #define PROP_UNSET(ft_props, property) ((ft_props) &= ~((uint32_t)(property))) #define TEXT_STYLE_TAG_MAX_SIZE (64 * 2) +#define FT_MAX_WIDTH_UNLIMITED INT_MAX FT_INTERNAL void get_style_tag_for_cell(const f_table_properties_t *props, @@ -1877,6 +1914,7 @@ struct f_cell_props { uint32_t properties_flags; unsigned int col_min_width; + unsigned int col_max_width; enum ft_text_alignment align; unsigned int cell_padding_top; unsigned int cell_padding_bottom; @@ -2344,7 +2382,12 @@ size_t hint_width_cell(const f_cell_t *cell, const f_context_t *context, enum f_ if (cell->str_buffer && cell->str_buffer->str.data) { result += buffer_text_visible_width(cell->str_buffer); } + /* todo: + * need to think about a case when MIN / MAX properties are set at the same + * time. + */ result = MAX(result, (size_t)get_cell_property_hierarchically(properties, row, column, FT_CPROP_MIN_WIDTH)); + result = MIN(result, (size_t)get_cell_property_hierarchically(properties, row, column, FT_CPROP_MAX_WIDTH)); if (geom == INTERN_REPR_GEOMETRY) { char cell_style_tag[TEXT_STYLE_TAG_MAX_SIZE]; @@ -4283,12 +4326,14 @@ static struct f_cell_props g_default_cell_properties = { FT_ANY_COLUMN, /* cell_col */ /* properties_flags */ - FT_CPROP_MIN_WIDTH | FT_CPROP_TEXT_ALIGN | FT_CPROP_TOP_PADDING + FT_CPROP_MIN_WIDTH | FT_CPROP_MAX_WIDTH + | FT_CPROP_TEXT_ALIGN | FT_CPROP_TOP_PADDING | FT_CPROP_BOTTOM_PADDING | FT_CPROP_LEFT_PADDING | FT_CPROP_RIGHT_PADDING | FT_CPROP_EMPTY_STR_HEIGHT | FT_CPROP_CONT_FG_COLOR | FT_CPROP_CELL_BG_COLOR | FT_CPROP_CONT_BG_COLOR | FT_CPROP_CELL_TEXT_STYLE | FT_CPROP_CONT_TEXT_STYLE, 0, /* col_min_width */ + FT_MAX_WIDTH_UNLIMITED, /* col_max_width */ FT_ALIGNED_LEFT, /* align */ 0, /* cell_padding_top */ 0, /* cell_padding_bottom */ @@ -4313,6 +4358,8 @@ static int get_prop_value_if_exists_otherwise_default(const struct f_cell_props switch (property) { case FT_CPROP_MIN_WIDTH: return cell_opts->col_min_width; + case FT_CPROP_MAX_WIDTH: + return cell_opts->col_max_width; case FT_CPROP_TEXT_ALIGN: return cell_opts->align; case FT_CPROP_TOP_PADDING: @@ -4446,6 +4493,11 @@ static f_status set_cell_property_impl(f_cell_props_t *opt, uint32_t property, i if (PROP_IS_SET(property, FT_CPROP_MIN_WIDTH)) { CHECK_NOT_NEGATIVE(value); opt->col_min_width = value; + } else if (PROP_IS_SET(property, FT_CPROP_MAX_WIDTH)) { + if (opt->cell_row != FT_ANY_ROW) + goto fort_fail; + CHECK_NOT_NEGATIVE(value); + opt->col_max_width = value; } else if (PROP_IS_SET(property, FT_CPROP_TEXT_ALIGN)) { opt->align = (enum ft_text_alignment)value; } else if (PROP_IS_SET(property, FT_CPROP_TOP_PADDING)) { @@ -4502,16 +4554,6 @@ f_status set_cell_property(f_cell_prop_container_t *cont, size_t row, size_t col return FT_ERROR; return set_cell_property_impl(opt, property, value); - /* - PROP_SET(opt->propertiess, property); - if (PROP_IS_SET(property, FT_CPROP_MIN_WIDTH)) { - opt->col_min_width = value; - } else if (PROP_IS_SET(property, FT_CPROP_TEXT_ALIGN)) { - opt->align = value; - } - - return FT_SUCCESS; - */ } @@ -6404,26 +6446,44 @@ size_t buffer_text_visible_width(const f_string_buffer_t *buffer) static void -buffer_substring(const f_string_buffer_t *buffer, size_t buffer_row, const void **begin, const void **end, ptrdiff_t *str_it_width) +buffer_substring(const f_string_buffer_t *buffer, size_t vis_width, size_t buffer_row, const void **begin, const void **end, ptrdiff_t *str_it_width) { switch (buffer->type) { case CHAR_BUF: str_n_substring(buffer->str.cstr, '\n', buffer_row, (const char **)begin, (const char **)end); - if ((*(const char **)begin) && (*(const char **)end)) + if ((*(const char **)begin) && (*(const char **)end)) { *str_it_width = str_iter_width(*(const char **)begin, *(const char **)end); + + if (*str_it_width > (ptrdiff_t)vis_width) { + *str_it_width = vis_width; + *end = (char *)*begin + vis_width; + } + } break; #ifdef FT_HAVE_WCHAR case W_CHAR_BUF: wstr_n_substring(buffer->str.wstr, L'\n', buffer_row, (const wchar_t **)begin, (const wchar_t **)end); - if ((*(const wchar_t **)begin) && (*(const wchar_t **)end)) + if ((*(const wchar_t **)begin) && (*(const wchar_t **)end)) { *str_it_width = wcs_iter_width(*(const wchar_t **)begin, *(const wchar_t **)end); + + if (*str_it_width > (ptrdiff_t)vis_width) { + *str_it_width = vis_width; + *end = (wchar_t *)*begin + vis_width; + } + } break; #endif /* FT_HAVE_WCHAR */ #ifdef FT_HAVE_UTF8 case UTF8_BUF: utf8_n_substring(buffer->str.u8str, '\n', buffer_row, begin, end); - if ((*(const char **)begin) && (*(const char **)end)) + if ((*(const char **)begin) && (*(const char **)end)) { *str_it_width = utf8_width(*begin, *end); + + if (*str_it_width > (ptrdiff_t)vis_width) { + *end = utf8forw((const void *)*begin, vis_width, &vis_width); + *str_it_width = vis_width; + } + } break; #endif /* FT_HAVE_UTF8 */ default: @@ -6464,6 +6524,13 @@ int buffer_printf(f_string_buffer_t *buffer, size_t buffer_row, f_conv_context_t f_table_properties_t *props = context->table_properties; size_t row = context->row; size_t column = context->column; + unsigned max_width_prop = (unsigned)get_cell_property_hierarchically(props, row, column, FT_CPROP_MAX_WIDTH); + size_t padding_left = get_cell_property_hierarchically(props, row, column, FT_CPROP_LEFT_PADDING); + size_t padding_right = get_cell_property_hierarchically(props, row, column, FT_CPROP_RIGHT_PADDING); + /* Max width includes paddings see comments in @hint_width_cell */ + size_t max_width = (max_width_prop >= FT_MAX_WIDTH_UNLIMITED) ? FT_MAX_WIDTH_UNLIMITED : max_width_prop - padding_left - padding_right; + if (max_width < vis_width) + return -1; if (buffer == NULL || buffer->str.data == NULL || buffer_row >= buffer_text_visible_height(buffer)) { @@ -6471,6 +6538,7 @@ int buffer_printf(f_string_buffer_t *buffer, size_t buffer_row, f_conv_context_t } size_t content_width = buffer_text_visible_width(buffer); + content_width = MIN(max_width, content_width); if (vis_width < content_width) return -1; @@ -6499,7 +6567,7 @@ int buffer_printf(f_string_buffer_t *buffer, size_t buffer_row, f_conv_context_t ptrdiff_t str_it_width = 0; const void *beg = NULL; const void *end = NULL; - buffer_substring(buffer, buffer_row, &beg, &end, &str_it_width); + buffer_substring(buffer, content_width, buffer_row, &beg, &end, &str_it_width); if (beg == NULL || end == NULL) return -1; if (str_it_width < 0 || content_width < (size_t)str_it_width) diff --git a/lib/fort.h b/lib/fort.h index efda242..dd38043 100644 --- a/lib/fort.h +++ b/lib/fort.h @@ -696,18 +696,19 @@ int ft_set_border_style(ft_table_t *table, const struct ft_border_style *style); * @{ */ #define FT_CPROP_MIN_WIDTH (0x01U << 0) /**< Minimum width */ -#define FT_CPROP_TEXT_ALIGN (0x01U << 1) /**< Text alignment */ -#define FT_CPROP_TOP_PADDING (0x01U << 2) /**< Top padding for cell content */ -#define FT_CPROP_BOTTOM_PADDING (0x01U << 3) /**< Bottom padding for cell content */ -#define FT_CPROP_LEFT_PADDING (0x01U << 4) /**< Left padding for cell content */ -#define FT_CPROP_RIGHT_PADDING (0x01U << 5) /**< Right padding for cell content */ -#define FT_CPROP_EMPTY_STR_HEIGHT (0x01U << 6) /**< Height of empty cell */ -#define FT_CPROP_ROW_TYPE (0x01U << 7) /**< Row type */ -#define FT_CPROP_CONT_FG_COLOR (0x01U << 8) /**< Cell content foreground text color */ -#define FT_CPROP_CELL_BG_COLOR (0x01U << 9) /**< Cell background color */ -#define FT_CPROP_CONT_BG_COLOR (0x01U << 10) /**< Cell content background color */ -#define FT_CPROP_CELL_TEXT_STYLE (0x01U << 11) /**< Cell text style */ -#define FT_CPROP_CONT_TEXT_STYLE (0x01U << 12) /**< Cell content text style */ +#define FT_CPROP_MAX_WIDTH (0x01U << 1) /**< Maximum width */ +#define FT_CPROP_TEXT_ALIGN (0x01U << 2) /**< Text alignment */ +#define FT_CPROP_TOP_PADDING (0x01U << 3) /**< Top padding for cell content */ +#define FT_CPROP_BOTTOM_PADDING (0x01U << 4) /**< Bottom padding for cell content */ +#define FT_CPROP_LEFT_PADDING (0x01U << 5) /**< Left padding for cell content */ +#define FT_CPROP_RIGHT_PADDING (0x01U << 6) /**< Right padding for cell content */ +#define FT_CPROP_EMPTY_STR_HEIGHT (0x01U << 7) /**< Height of empty cell */ +#define FT_CPROP_ROW_TYPE (0x01U << 8) /**< Row type */ +#define FT_CPROP_CONT_FG_COLOR (0x01U << 9) /**< Cell content foreground text color */ +#define FT_CPROP_CELL_BG_COLOR (0x01U << 10) /**< Cell background color */ +#define FT_CPROP_CONT_BG_COLOR (0x01U << 11) /**< Cell content background color */ +#define FT_CPROP_CELL_TEXT_STYLE (0x01U << 12) /**< Cell text style */ +#define FT_CPROP_CONT_TEXT_STYLE (0x01U << 13) /**< Cell content text style */ /** @} */ diff --git a/src/cell.c b/src/cell.c index 522199a..6f69b5e 100644 --- a/src/cell.c +++ b/src/cell.c @@ -85,7 +85,12 @@ size_t hint_width_cell(const f_cell_t *cell, const f_context_t *context, enum f_ if (cell->str_buffer && cell->str_buffer->str.data) { result += buffer_text_visible_width(cell->str_buffer); } + /* todo: + * need to think about a case when MIN / MAX properties are set at the same + * time. + */ result = MAX(result, (size_t)get_cell_property_hierarchically(properties, row, column, FT_CPROP_MIN_WIDTH)); + result = MIN(result, (size_t)get_cell_property_hierarchically(properties, row, column, FT_CPROP_MAX_WIDTH)); if (geom == INTERN_REPR_GEOMETRY) { char cell_style_tag[TEXT_STYLE_TAG_MAX_SIZE]; diff --git a/src/fort.h b/src/fort.h index efda242..dd38043 100644 --- a/src/fort.h +++ b/src/fort.h @@ -696,18 +696,19 @@ int ft_set_border_style(ft_table_t *table, const struct ft_border_style *style); * @{ */ #define FT_CPROP_MIN_WIDTH (0x01U << 0) /**< Minimum width */ -#define FT_CPROP_TEXT_ALIGN (0x01U << 1) /**< Text alignment */ -#define FT_CPROP_TOP_PADDING (0x01U << 2) /**< Top padding for cell content */ -#define FT_CPROP_BOTTOM_PADDING (0x01U << 3) /**< Bottom padding for cell content */ -#define FT_CPROP_LEFT_PADDING (0x01U << 4) /**< Left padding for cell content */ -#define FT_CPROP_RIGHT_PADDING (0x01U << 5) /**< Right padding for cell content */ -#define FT_CPROP_EMPTY_STR_HEIGHT (0x01U << 6) /**< Height of empty cell */ -#define FT_CPROP_ROW_TYPE (0x01U << 7) /**< Row type */ -#define FT_CPROP_CONT_FG_COLOR (0x01U << 8) /**< Cell content foreground text color */ -#define FT_CPROP_CELL_BG_COLOR (0x01U << 9) /**< Cell background color */ -#define FT_CPROP_CONT_BG_COLOR (0x01U << 10) /**< Cell content background color */ -#define FT_CPROP_CELL_TEXT_STYLE (0x01U << 11) /**< Cell text style */ -#define FT_CPROP_CONT_TEXT_STYLE (0x01U << 12) /**< Cell content text style */ +#define FT_CPROP_MAX_WIDTH (0x01U << 1) /**< Maximum width */ +#define FT_CPROP_TEXT_ALIGN (0x01U << 2) /**< Text alignment */ +#define FT_CPROP_TOP_PADDING (0x01U << 3) /**< Top padding for cell content */ +#define FT_CPROP_BOTTOM_PADDING (0x01U << 4) /**< Bottom padding for cell content */ +#define FT_CPROP_LEFT_PADDING (0x01U << 5) /**< Left padding for cell content */ +#define FT_CPROP_RIGHT_PADDING (0x01U << 6) /**< Right padding for cell content */ +#define FT_CPROP_EMPTY_STR_HEIGHT (0x01U << 7) /**< Height of empty cell */ +#define FT_CPROP_ROW_TYPE (0x01U << 8) /**< Row type */ +#define FT_CPROP_CONT_FG_COLOR (0x01U << 9) /**< Cell content foreground text color */ +#define FT_CPROP_CELL_BG_COLOR (0x01U << 10) /**< Cell background color */ +#define FT_CPROP_CONT_BG_COLOR (0x01U << 11) /**< Cell content background color */ +#define FT_CPROP_CELL_TEXT_STYLE (0x01U << 12) /**< Cell text style */ +#define FT_CPROP_CONT_TEXT_STYLE (0x01U << 13) /**< Cell content text style */ /** @} */ diff --git a/src/properties.c b/src/properties.c index 96ab525..4639613 100644 --- a/src/properties.c +++ b/src/properties.c @@ -246,12 +246,14 @@ static struct f_cell_props g_default_cell_properties = { FT_ANY_COLUMN, /* cell_col */ /* properties_flags */ - FT_CPROP_MIN_WIDTH | FT_CPROP_TEXT_ALIGN | FT_CPROP_TOP_PADDING + FT_CPROP_MIN_WIDTH | FT_CPROP_MAX_WIDTH + | FT_CPROP_TEXT_ALIGN | FT_CPROP_TOP_PADDING | FT_CPROP_BOTTOM_PADDING | FT_CPROP_LEFT_PADDING | FT_CPROP_RIGHT_PADDING | FT_CPROP_EMPTY_STR_HEIGHT | FT_CPROP_CONT_FG_COLOR | FT_CPROP_CELL_BG_COLOR | FT_CPROP_CONT_BG_COLOR | FT_CPROP_CELL_TEXT_STYLE | FT_CPROP_CONT_TEXT_STYLE, 0, /* col_min_width */ + FT_MAX_WIDTH_UNLIMITED, /* col_max_width */ FT_ALIGNED_LEFT, /* align */ 0, /* cell_padding_top */ 0, /* cell_padding_bottom */ @@ -276,6 +278,8 @@ static int get_prop_value_if_exists_otherwise_default(const struct f_cell_props switch (property) { case FT_CPROP_MIN_WIDTH: return cell_opts->col_min_width; + case FT_CPROP_MAX_WIDTH: + return cell_opts->col_max_width; case FT_CPROP_TEXT_ALIGN: return cell_opts->align; case FT_CPROP_TOP_PADDING: @@ -409,6 +413,11 @@ static f_status set_cell_property_impl(f_cell_props_t *opt, uint32_t property, i if (PROP_IS_SET(property, FT_CPROP_MIN_WIDTH)) { CHECK_NOT_NEGATIVE(value); opt->col_min_width = value; + } else if (PROP_IS_SET(property, FT_CPROP_MAX_WIDTH)) { + if (opt->cell_row != FT_ANY_ROW) + goto fort_fail; + CHECK_NOT_NEGATIVE(value); + opt->col_max_width = value; } else if (PROP_IS_SET(property, FT_CPROP_TEXT_ALIGN)) { opt->align = (enum ft_text_alignment)value; } else if (PROP_IS_SET(property, FT_CPROP_TOP_PADDING)) { @@ -465,16 +474,6 @@ f_status set_cell_property(f_cell_prop_container_t *cont, size_t row, size_t col return FT_ERROR; return set_cell_property_impl(opt, property, value); - /* - PROP_SET(opt->propertiess, property); - if (PROP_IS_SET(property, FT_CPROP_MIN_WIDTH)) { - opt->col_min_width = value; - } else if (PROP_IS_SET(property, FT_CPROP_TEXT_ALIGN)) { - opt->align = value; - } - - return FT_SUCCESS; - */ } diff --git a/src/properties.h b/src/properties.h index 48901c0..f07c45b 100644 --- a/src/properties.h +++ b/src/properties.h @@ -10,6 +10,7 @@ #define PROP_UNSET(ft_props, property) ((ft_props) &= ~((uint32_t)(property))) #define TEXT_STYLE_TAG_MAX_SIZE (64 * 2) +#define FT_MAX_WIDTH_UNLIMITED INT_MAX FT_INTERNAL void get_style_tag_for_cell(const f_table_properties_t *props, @@ -34,6 +35,7 @@ struct f_cell_props { uint32_t properties_flags; unsigned int col_min_width; + unsigned int col_max_width; enum ft_text_alignment align; unsigned int cell_padding_top; unsigned int cell_padding_bottom; diff --git a/src/string_buffer.c b/src/string_buffer.c index dfafbcc..9e49bda 100644 --- a/src/string_buffer.c +++ b/src/string_buffer.c @@ -530,26 +530,44 @@ size_t buffer_text_visible_width(const f_string_buffer_t *buffer) static void -buffer_substring(const f_string_buffer_t *buffer, size_t buffer_row, const void **begin, const void **end, ptrdiff_t *str_it_width) +buffer_substring(const f_string_buffer_t *buffer, size_t vis_width, size_t buffer_row, const void **begin, const void **end, ptrdiff_t *str_it_width) { switch (buffer->type) { case CHAR_BUF: str_n_substring(buffer->str.cstr, '\n', buffer_row, (const char **)begin, (const char **)end); - if ((*(const char **)begin) && (*(const char **)end)) + if ((*(const char **)begin) && (*(const char **)end)) { *str_it_width = str_iter_width(*(const char **)begin, *(const char **)end); + + if (*str_it_width > (ptrdiff_t)vis_width) { + *str_it_width = vis_width; + *end = (char *)*begin + vis_width; + } + } break; #ifdef FT_HAVE_WCHAR case W_CHAR_BUF: wstr_n_substring(buffer->str.wstr, L'\n', buffer_row, (const wchar_t **)begin, (const wchar_t **)end); - if ((*(const wchar_t **)begin) && (*(const wchar_t **)end)) + if ((*(const wchar_t **)begin) && (*(const wchar_t **)end)) { *str_it_width = wcs_iter_width(*(const wchar_t **)begin, *(const wchar_t **)end); + + if (*str_it_width > (ptrdiff_t)vis_width) { + *str_it_width = vis_width; + *end = (wchar_t *)*begin + vis_width; + } + } break; #endif /* FT_HAVE_WCHAR */ #ifdef FT_HAVE_UTF8 case UTF8_BUF: utf8_n_substring(buffer->str.u8str, '\n', buffer_row, begin, end); - if ((*(const char **)begin) && (*(const char **)end)) + if ((*(const char **)begin) && (*(const char **)end)) { *str_it_width = utf8_width(*begin, *end); + + if (*str_it_width > (ptrdiff_t)vis_width) { + *end = utf8forw((const void *)*begin, vis_width, &vis_width); + *str_it_width = vis_width; + } + } break; #endif /* FT_HAVE_UTF8 */ default: @@ -590,6 +608,13 @@ int buffer_printf(f_string_buffer_t *buffer, size_t buffer_row, f_conv_context_t f_table_properties_t *props = context->table_properties; size_t row = context->row; size_t column = context->column; + unsigned max_width_prop = (unsigned)get_cell_property_hierarchically(props, row, column, FT_CPROP_MAX_WIDTH); + size_t padding_left = get_cell_property_hierarchically(props, row, column, FT_CPROP_LEFT_PADDING); + size_t padding_right = get_cell_property_hierarchically(props, row, column, FT_CPROP_RIGHT_PADDING); + /* Max width includes paddings see comments in @hint_width_cell */ + size_t max_width = (max_width_prop >= FT_MAX_WIDTH_UNLIMITED) ? FT_MAX_WIDTH_UNLIMITED : max_width_prop - padding_left - padding_right; + if (max_width < vis_width) + return -1; if (buffer == NULL || buffer->str.data == NULL || buffer_row >= buffer_text_visible_height(buffer)) { @@ -597,6 +622,7 @@ int buffer_printf(f_string_buffer_t *buffer, size_t buffer_row, f_conv_context_t } size_t content_width = buffer_text_visible_width(buffer); + content_width = MIN(max_width, content_width); if (vis_width < content_width) return -1; @@ -625,7 +651,7 @@ int buffer_printf(f_string_buffer_t *buffer, size_t buffer_row, f_conv_context_t ptrdiff_t str_it_width = 0; const void *beg = NULL; const void *end = NULL; - buffer_substring(buffer, buffer_row, &beg, &end, &str_it_width); + buffer_substring(buffer, content_width, buffer_row, &beg, &end, &str_it_width); if (beg == NULL || end == NULL) return -1; if (str_it_width < 0 || content_width < (size_t)str_it_width) diff --git a/src/utf8.h b/src/utf8.h index b1ec66e..f76d5ee 100644 --- a/src/utf8.h +++ b/src/utf8.h @@ -121,6 +121,12 @@ utf8_nonnull utf8_pure utf8_weak size_t utf8len(const void *str); // Visible width of utf8string. utf8_nonnull utf8_pure utf8_weak size_t utf8width(const void *str); +// Forward pointer by `vis_w` visible codepoints +// (or less if end of string is encountered). Sets `width` to visible width of +// string between `str` and returned value. +utf8_nonnull utf8_pure utf8_weak const void *utf8forw(const void *str, + size_t vis_w, size_t *width); + // Visible width of codepoint. utf8_nonnull utf8_pure utf8_weak int utf8cwidth(utf8_int32_t c); @@ -544,6 +550,36 @@ size_t utf8width(const void *str) return length; } +const void *utf8forw(const void *str, size_t vis_w, size_t *width) +{ + size_t length = 0; + utf8_int32_t c = 0; + /* We need to store previous value of str + * because some codepoints may occupy more than + * one symbol and during iteration we may exceed vis_w. + */ + const void *prev_str = str; + size_t old_length = 0; + + str = utf8codepoint(str, &c); + while (c != 0) { + old_length = length; + length += utf8cwidth(c); + if (length > vis_w) + break; + prev_str = str; + str = utf8codepoint(str, &c); + } + + if (length > vis_w) { + *width = old_length; + return prev_str; + } else { + *width = length; + return str; + } +} + int utf8ncasecmp(const void *src1, const void *src2, size_t n) { utf8_int32_t src1_cp, src2_cp, src1_orig_cp, src2_orig_cp; diff --git a/tests/bb_tests/test_table_properties.c b/tests/bb_tests/test_table_properties.c index b3defae..e227af6 100644 --- a/tests/bb_tests/test_table_properties.c +++ b/tests/bb_tests/test_table_properties.c @@ -485,7 +485,7 @@ void test_table_cell_properties(void) - WHEN("Set table width and column alignment") { + WHEN("Set table min width and column alignment") { set_test_properties_as_default(); @@ -553,6 +553,167 @@ void test_table_cell_properties(void) ft_destroy_table(table); } + WHEN("Set table max width for a particular cell is impossible") { + table = ft_create_table(); + int status = ft_set_cell_prop(table, 0, 0, FT_CPROP_MAX_WIDTH, 5); + assert_true(status == FT_EINVAL); + ft_destroy_table(table); + } + + WHEN("Set table max width 1") { + set_test_properties_as_default(); + table = ft_create_table(); + ft_write_ln(table, "123456789"); + + int status = FT_SUCCESS; + status |= ft_set_cell_prop(table, FT_ANY_ROW, 0, FT_CPROP_MAX_WIDTH, 5); + assert_true(status == FT_SUCCESS); + + const char *table_str = ft_to_string(table); + assert_true(table_str != NULL); + const char *table_str_etalon = + "+-----+\n" + "| |\n" + "| 123 |\n" + "| |\n" + "+-----+\n"; + assert_str_equal(table_str, table_str_etalon); + ft_destroy_table(table); + } + +#ifdef FT_HAVE_WCHAR + WHEN("Set table max width 1") { + set_test_properties_as_default(); + table = ft_create_table(); + ft_wwrite_ln(table, L"123456789"); + + int status = FT_SUCCESS; + status |= ft_set_cell_prop(table, FT_ANY_ROW, 0, FT_CPROP_MAX_WIDTH, 5); + assert_true(status == FT_SUCCESS); + + const wchar_t *table_str = ft_to_wstring(table); + assert_true(table_str != NULL); + const wchar_t *table_str_etalon = + L"+-----+\n" + L"| |\n" + L"| 123 |\n" + L"| |\n" + L"+-----+\n"; + assert_wcs_equal(table_str, table_str_etalon); + ft_destroy_table(table); + } +#endif /* ifdef FT_HAVE_WCHAR */ + +#ifdef FT_HAVE_UTF8 + WHEN("Set table max width 1") { + set_test_properties_as_default(); + table = ft_create_table(); + ft_u8write_ln(table, "視野無限廣窗外有"); + + int status = FT_SUCCESS; + status |= ft_set_cell_prop(table, FT_ANY_ROW, 0, FT_CPROP_MAX_WIDTH, 5); + assert_true(status == FT_SUCCESS); + + const char *table_str = ft_to_u8string(table); + assert_true(table_str != NULL); + const char *table_str_etalon = + "+-----+\n" + "| |\n" + "| 視 |\n" + "| |\n" + "+-----+\n"; + assert_str_equal(table_str, table_str_etalon); + ft_destroy_table(table); + } +#endif /* ifdef FT_HAVE_UTF8 */ + + + WHEN("Set table max width 2") { + set_test_properties_as_default(); + table = ft_create_table(); + ft_write_ln(table, "123456789", "123456789"); + ft_write_ln(table, "123456789", "123456789"); + + int status = FT_SUCCESS; + status |= ft_set_cell_prop(table, FT_ANY_ROW, 0, FT_CPROP_MAX_WIDTH, 5); + status |= ft_set_cell_prop(table, FT_ANY_ROW, 1, FT_CPROP_MAX_WIDTH, 7); + assert_true(status == FT_SUCCESS); + + const char *table_str = ft_to_string(table); + assert_true(table_str != NULL); + const char *table_str_etalon = + "+-----+-------+\n" + "| | |\n" + "| 123 | 12345 |\n" + "| | |\n" + "+-----+-------+\n" + "| | |\n" + "| 123 | 12345 |\n" + "| | |\n" + "+-----+-------+\n"; + assert_str_equal(table_str, table_str_etalon); + ft_destroy_table(table); + } + +#ifdef FT_HAVE_WCHAR + WHEN("Set table max width 2") { + set_test_properties_as_default(); + table = ft_create_table(); + ft_wwrite_ln(table, L"123456789", L"123456789"); + ft_wwrite_ln(table, L"123456789", L"123456789"); + + int status = FT_SUCCESS; + status |= ft_set_cell_prop(table, FT_ANY_ROW, 0, FT_CPROP_MAX_WIDTH, 5); + status |= ft_set_cell_prop(table, FT_ANY_ROW, 1, FT_CPROP_MAX_WIDTH, 7); + assert_true(status == FT_SUCCESS); + + const wchar_t *table_str = ft_to_wstring(table); + assert_true(table_str != NULL); + const wchar_t *table_str_etalon = + L"+-----+-------+\n" + L"| | |\n" + L"| 123 | 12345 |\n" + L"| | |\n" + L"+-----+-------+\n" + L"| | |\n" + L"| 123 | 12345 |\n" + L"| | |\n" + L"+-----+-------+\n"; + assert_wcs_equal(table_str, table_str_etalon); + ft_destroy_table(table); + } +#endif /* ifdef FT_HAVE_WCHAR */ + +#ifdef FT_HAVE_UTF8 + WHEN("Set table max width 2") { + set_test_properties_as_default(); + table = ft_create_table(); + ft_u8write_ln(table, "視野無限廣窗外有", "視野無限廣窗外有"); + + int status = FT_SUCCESS; + /* We test 2 different cases: + * 1 - trailing wide character doesn't fit into odd max width and therefore + * library have to add a space at the end + * 2 - wide character fits into the even max width + */ + status |= ft_set_cell_prop(table, FT_ANY_ROW, 0, FT_CPROP_MAX_WIDTH, 5); + status |= ft_set_cell_prop(table, FT_ANY_ROW, 1, FT_CPROP_MAX_WIDTH, 6); + assert_true(status == FT_SUCCESS); + + const char *table_str = ft_to_u8string(table); + assert_true(table_str != NULL); + const char *table_str_etalon = + "+-----+------+\n" + "| | |\n" + "| 視 | 視野 |\n" + "| | |\n" + "+-----+------+\n"; + assert_str_equal(table_str, table_str_etalon); + ft_destroy_table(table); + } +#endif /* ifdef FT_HAVE_UTF8 */ + + WHEN("Multiline cell") { set_test_properties_as_default();