/*
libfort

MIT License

Copyright (c) 2017 - 2019 Seleznev Anton

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/* The file was GENERATED by an amalgamation script.*/
/* DO NOT EDIT BY HAND!!! */


#define FT_AMALGAMED_SOURCE /* Macros to make internal libfort functions static */


/********************************************************
   Begin of file "fort_utils.h"
 ********************************************************/

#ifndef FORT_IMPL_H
#define FORT_IMPL_H

#if defined(_MSC_VER)
#define _CRT_SECURE_NO_WARNINGS /* To disable warnings for unsafe functions */
#endif

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include "fort.h"

/* Define FT_INTERNAL to make internal libfort functions static
 * in the result amalgamed source file.
 */
#ifdef FT_AMALGAMED_SOURCE
#define FT_INTERNAL static
#else
#define FT_INTERNAL
#endif /* FT_AMALGAMED_SORCE */


#define FORT_DEFAULT_COL_SEPARATOR '|'
extern char g_col_separator;

#define FORT_COL_SEPARATOR_LENGTH 1

#define FORT_UNUSED  __attribute__((unused))

#define F_MALLOC fort_malloc
#define F_FREE fort_free
#define F_CALLOC fort_calloc
#define F_REALLOC fort_realloc
#define F_STRDUP fort_strdup
#define F_WCSDUP fort_wcsdup

#define F_CREATE(type) ((type *)F_CALLOC(sizeof(type), 1))

#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))


enum PolicyOnNull {
    Create,
    DoNotCreate
};


enum F_BOOL {
    F_FALSE = 0,
    F_TRUE = 1
};


#define FT_STR_2_CAT_(arg1, arg2) \
    arg1##arg2
#define FT_STR_2_CAT(arg1, arg2) \
    FT_STR_2_CAT_(arg1, arg2)

#define UNIQUE_NAME_(prefix) \
    FT_STR_2_CAT(prefix,__COUNTER__)
#define UNIQUE_NAME(prefix) \
    UNIQUE_NAME_(prefix)



/*****************************************************************************
 *               LOGGER
 *****************************************************************************/
#define SYS_LOG_ERROR(...)



/*****************************************************************************
 *               DEFAULT_SIZES
 * ***************************************************************************/
#define DEFAULT_STR_BUF_SIZE 1024
#define DEFAULT_VECTOR_CAPACITY 10



struct fort_table_properties;
struct fort_column_properties;
struct fort_row;
struct vector;
struct fort_cell;
struct string_buffer;
struct separator {
    int enabled;
};

typedef struct fort_table_properties fort_table_properties_t;
struct fort_context {
    fort_table_properties_t *table_properties;
    size_t row;
    size_t column;
};
typedef struct fort_context context_t;
typedef struct fort_column_properties fort_column_properties_t;
typedef struct vector vector_t;
typedef struct fort_cell fort_cell_t;
typedef struct string_buffer string_buffer_t;
typedef struct fort_row fort_row_t;
/*typedef struct ft_table ft_table_t;*/
typedef struct separator separator_t;

enum CellType {
    CommonCell,
    GroupMasterCell,
    GroupSlaveCell
};

enum request_geom_type {
    VISIBLE_GEOMETRY,
    INTERN_REPR_GEOMETRY
};

/*****************************************************************************
 *               LIBFORT helpers
 *****************************************************************************/

extern void *(*fort_malloc)(size_t size);
extern void (*fort_free)(void *ptr);
extern void *(*fort_calloc)(size_t nmemb, size_t size);
extern void *(*fort_realloc)(void *ptr, size_t size);

FT_INTERNAL
void set_memory_funcs(void *(*f_malloc)(size_t size), void (*f_free)(void *ptr));

FT_INTERNAL
char *fort_strdup(const char *str);

FT_INTERNAL
size_t number_of_columns_in_format_string(const char *fmt);

#if defined(FT_HAVE_WCHAR)
FT_INTERNAL
wchar_t *fort_wcsdup(const wchar_t *str);

FT_INTERNAL
size_t number_of_columns_in_format_wstring(const wchar_t *fmt);
#endif

FT_INTERNAL
int snprint_n_strings(char *buf, size_t length, size_t n, const char *str);

#if defined(FT_HAVE_WCHAR)
FT_INTERNAL
int wsnprint_n_string(wchar_t *buf, size_t length, size_t n, const char *str);
#endif


#define CHCK_RSLT_ADD_TO_WRITTEN(statement) \
    do { \
        tmp = statement; \
        if (tmp < 0) {\
            goto clear; \
        } \
        written += (size_t)tmp; \
    } while(0)

#define CHCK_RSLT_ADD_TO_INVISIBLE_WRITTEN(statement) \
    do { \
        tmp = statement; \
        if (tmp < 0) {\
            goto clear; \
        } \
        invisible_written += (size_t)tmp; \
    } while(0)


#define CHECK_NOT_NEGATIVE(x) \
    do { if ((x) < 0) goto fort_fail; } while (0)

#endif /* FORT_IMPL_H */

/********************************************************
   End of file "fort_utils.h"
 ********************************************************/


/********************************************************
   Begin of file "vector.h"
 ********************************************************/

#ifndef VECTOR_H
#define VECTOR_H

/* #include "fort_utils.h" */ /* Commented by amalgamation script */


#define INVALID_VEC_INDEX ((size_t) -1)

FT_INTERNAL
vector_t *create_vector(size_t item_size, size_t capacity);

FT_INTERNAL
void destroy_vector(vector_t *);

FT_INTERNAL
size_t vector_size(const vector_t *);

FT_INTERNAL
size_t vector_capacity(const vector_t *);

FT_INTERNAL
int vector_push(vector_t *, const void *item);

FT_INTERNAL
const void *vector_at_c(const vector_t *vector, size_t index);

FT_INTERNAL
void *vector_at(vector_t *, size_t index);

FT_INTERNAL
fort_status_t vector_swap(vector_t *cur_vec, vector_t *mv_vec, size_t pos);

/*
#define FOR_EACH_(type, item, vector, index_name) \
    size_t index_name = 0; \
    for (index_name = 0; (index_name < vector_size(vector)) ? ((item = *(type*)vector_at(vector, index_name)), 1) : 0;  ++index_name)

#define FOR_EACH(type, item, vector) \
    FOR_EACH_(type, item, vector, UNIQUE_NAME(i))
*/


#ifdef FT_TEST_BUILD
vector_t *copy_vector(vector_t *);
size_t vector_index_of(const vector_t *, const void *item);
int vector_erase(vector_t *, size_t index);
void vector_clear(vector_t *);
#endif

#endif /* VECTOR_H */

/********************************************************
   End of file "vector.h"
 ********************************************************/


/********************************************************
   Begin of file "wcwidth.h"
 ********************************************************/

#ifndef WCWIDTH_H
#define WCWIDTH_H

/* #include "fort_utils.h" */ /* Commented by amalgamation script */

#ifdef FT_HAVE_WCHAR
#include <wchar.h>

FT_INTERNAL
int mk_wcswidth(const wchar_t *pwcs, size_t n);

#endif /* FT_HAVE_WCHAR */

#endif /* WCWIDTH_H */

/********************************************************
   End of file "wcwidth.h"
 ********************************************************/


/********************************************************
   Begin of file "string_buffer.h"
 ********************************************************/

#ifndef STRING_BUFFER_H
#define STRING_BUFFER_H

/* #include "fort_utils.h" */ /* Commented by amalgamation script */


/*****************************************************************************
 *               STRING BUFFER
 * ***************************************************************************/
enum str_buf_type {
    CharBuf,
#ifdef FT_HAVE_WCHAR
    WCharBuf
#endif /* FT_HAVE_WCHAR */
};

struct string_buffer {
    union {
        char *cstr;
        wchar_t *wstr;
        void *data;
    } str;
    size_t data_sz;
    enum str_buf_type type;
};

FT_INTERNAL
string_buffer_t *create_string_buffer(size_t number_of_chars, enum str_buf_type type);

FT_INTERNAL
void destroy_string_buffer(string_buffer_t *buffer);

FT_INTERNAL
string_buffer_t *copy_string_buffer(const string_buffer_t *buffer);

FT_INTERNAL
fort_status_t realloc_string_buffer_without_copy(string_buffer_t *buffer);

FT_INTERNAL
fort_status_t fill_buffer_from_string(string_buffer_t *buffer, const char *str);

#ifdef FT_HAVE_WCHAR
FT_INTERNAL
fort_status_t fill_buffer_from_wstring(string_buffer_t *buffer, const wchar_t *str);
#endif /* FT_HAVE_WCHAR */

FT_INTERNAL
size_t buffer_text_height(const string_buffer_t *buffer);

FT_INTERNAL
size_t string_buffer_capacity(const string_buffer_t *buffer);

FT_INTERNAL
void *buffer_get_data(string_buffer_t *buffer);

FT_INTERNAL
size_t buffer_text_width(const string_buffer_t *buffer);

FT_INTERNAL
int buffer_printf(string_buffer_t *buffer, size_t buffer_row, char *buf, size_t total_buf_len,
                  const context_t *context, const char *content_style_tag, const char *reset_content_style_tag);

#ifdef FT_HAVE_WCHAR
FT_INTERNAL
int buffer_wprintf(string_buffer_t *buffer, size_t buffer_row, wchar_t *buf, size_t total_buf_len,
                   const context_t *context, const char *content_style_tag, const char *reset_content_style_tag);
#endif /* FT_HAVE_WCHAR */

#endif /* STRING_BUFFER_H */

/********************************************************
   End of file "string_buffer.h"
 ********************************************************/


/********************************************************
   Begin of file "properties.h"
 ********************************************************/

#ifndef PROPERTIES_H
#define PROPERTIES_H

/* #include "fort_utils.h" */ /* Commented by amalgamation script */
#include <stdint.h>
#include <limits.h>

#define PROP_IS_SET(ft_props, property) ((ft_props) & (property))
#define PROP_SET(ft_props, property) ((ft_props) |=(property))
#define PROP_UNSET(ft_props, property) ((ft_props) &= ~((uint32_t)(property)))

#define TEXT_STYLE_TAG_MAX_SIZE (64 * 2)

FT_INTERNAL
void get_style_tag_for_cell(const fort_table_properties_t *props,
                            size_t row, size_t col, char *style_tag, size_t sz);

FT_INTERNAL
void get_reset_style_tag_for_cell(const fort_table_properties_t *props,
                                  size_t row, size_t col, char *style_tag, size_t sz);

FT_INTERNAL
void get_style_tag_for_content(const fort_table_properties_t *props,
                               size_t row, size_t col, char *style_tag, size_t sz);

FT_INTERNAL
void get_reset_style_tag_for_content(const fort_table_properties_t *props,
                                     size_t row, size_t col, char *style_tag, size_t sz);


struct fort_cell_props {
    size_t cell_row;
    size_t cell_col;
    uint32_t properties_flags;

    unsigned int col_min_width;
    enum ft_text_alignment align;
    unsigned int cell_padding_top;
    unsigned int cell_padding_bottom;
    unsigned int cell_padding_left;
    unsigned int cell_padding_right;
    unsigned int cell_empty_string_height;
    enum ft_row_type row_type;
    unsigned int content_fg_color_number;
    unsigned int content_bg_color_number;
    unsigned int cell_bg_color_number;
    enum ft_text_style cell_text_style;
    enum ft_text_style content_text_style;
};

typedef struct fort_cell_props fort_cell_props_t;
typedef vector_t fort_cell_prop_container_t;

FT_INTERNAL
fort_cell_prop_container_t *create_cell_prop_container(void);

FT_INTERNAL
void destroy_cell_prop_container(fort_cell_prop_container_t *cont);

FT_INTERNAL
const fort_cell_props_t *cget_cell_prop(const fort_cell_prop_container_t *cont, size_t row, size_t col);

FT_INTERNAL
fort_cell_props_t *get_cell_prop_and_create_if_not_exists(fort_cell_prop_container_t *cont, size_t row, size_t col);

FT_INTERNAL
fort_status_t set_cell_property(fort_cell_prop_container_t *cont, size_t row, size_t col, uint32_t property, int value);

FT_INTERNAL
int get_cell_property_value_hierarcial(const fort_table_properties_t *properties, size_t row, size_t column, uint32_t property);

FT_INTERNAL
fort_status_t set_default_cell_property(uint32_t property, int value);


/*         TABLE BORDER DESСRIPTION
 *
 *
 *   TL TT TT TT TV TT TT TT TT TT TT TT TR
 *   LL          IV                      RR
 *   LL          IV                      RR
 *   LH IH IH IH II IH IH IH TI IH IH IH RH
 *   LL          IV          IV          RR
 *   LL          IV          IV          RR
 *   LL          LI IH IH IH RI          RH
 *   LL          IV          IV          RR
 *   LL          IV          IV          RR
 *   LH IH IH IH BI IH IH IH II IH IH IH RH
 *   LL                      IV          RR
 *   LL                      IV          RR
 *   BL BB BB BB BV BB BB BB BV BB BB BB BR
 */


/*      HORIZONTAL SEPARATOR DESCRIPTION
 *
 *
 *   TL TT TT TT TV TT TT TT TV TT TT TT TR        <----- TopSeparator
 *   LL          IV          IV          RR
 *   LH IH IH IH II IH IH IH II IH IH IH RH        <----- InsideSeparator
 *   LL          IV          IV          RR
 *   BL BB BB BB BV BB BB BB BV BB BB BB BR        <----- BottomSeparator
 */

enum HorSeparatorPos {
    TopSeparator,
    InsideSeparator,
    BottomSeparator
};

enum BorderItemPos {
    TL_bip = 0,
    TT_bip = 1,
    TV_bip = 2,
    TR_bip = 3,

    LL_bip = 4,
    IV_bip = 5,
    RR_bip = 6,

    LH_bip = 7,
    IH_bip = 8,
    II_bip = 9,
    RH_bip = 10,

    BL_bip = 11,
    BB_bip = 12,
    BV_bip = 13,
    BR_bip = 14,

    LI_bip = 15,
    TI_bip = 16,
    RI_bip = 17,
    BI_bip = 18,

    BorderItemPosSize
};


enum SeparatorItemPos {
    LH_sip = 0,
    IH_sip = 1,
    II_sip = 2,
    RH_sip = 3,

    TI_sip = 4,
    BI_sip = 5,

    SepratorItemPosSize
};


struct fort_border_style {
    const char *border_chars[BorderItemPosSize];
    const char *header_border_chars[BorderItemPosSize];
    const char *separator_chars[SepratorItemPosSize];
};
extern struct fort_border_style FORT_BASIC_STYLE;
extern struct fort_border_style FORT_BASIC2_STYLE;
extern struct fort_border_style FORT_SIMPLE_STYLE;
extern struct fort_border_style FORT_PLAIN_STYLE;
extern struct fort_border_style FORT_DOT_STYLE;
extern struct fort_border_style FORT_EMPTY_STYLE;
extern struct fort_border_style FORT_EMPTY2_STYLE;
extern struct fort_border_style FORT_SOLID_STYLE;
extern struct fort_border_style FORT_SOLID_ROUND_STYLE;
extern struct fort_border_style FORT_NICE_STYLE;
extern struct fort_border_style FORT_DOUBLE_STYLE;
extern struct fort_border_style FORT_DOUBLE2_STYLE;
extern struct fort_border_style FORT_BOLD_STYLE;
extern struct fort_border_style FORT_BOLD2_STYLE;
extern struct fort_border_style FORT_FRAME_STYLE;


struct fort_entire_table_properties {
    unsigned int left_margin;
    unsigned int top_margin;
    unsigned int right_margin;
    unsigned int bottom_margin;
};
typedef struct fort_entire_table_properties fort_entire_table_properties_t;
extern fort_entire_table_properties_t g_entire_table_properties;

FT_INTERNAL
fort_status_t set_entire_table_property(fort_table_properties_t *table_properties, uint32_t property, int value);

FT_INTERNAL
fort_status_t set_default_entire_table_property(uint32_t property, int value);

struct fort_table_properties {
    struct fort_border_style border_style;
    fort_cell_prop_container_t *cell_properties;
    fort_entire_table_properties_t entire_table_properties;
};
extern fort_table_properties_t g_table_properties;

FT_INTERNAL
size_t max_border_elem_strlen(struct fort_table_properties *);

FT_INTERNAL
fort_table_properties_t *create_table_properties(void);

FT_INTERNAL
void destroy_table_properties(fort_table_properties_t *properties);

FT_INTERNAL
fort_table_properties_t *copy_table_properties(const fort_table_properties_t *property);

#endif /* PROPERTIES_H */

/********************************************************
   End of file "properties.h"
 ********************************************************/


/********************************************************
   Begin of file "cell.h"
 ********************************************************/

#ifndef CELL_H
#define CELL_H

/* #include "fort_utils.h" */ /* Commented by amalgamation script */

FT_INTERNAL
fort_cell_t *create_cell(void);

FT_INTERNAL
void destroy_cell(fort_cell_t *cell);

FT_INTERNAL
fort_cell_t *copy_cell(fort_cell_t *cell);

FT_INTERNAL
size_t hint_width_cell(const fort_cell_t *cell, const context_t *context, enum request_geom_type geom);

FT_INTERNAL
size_t hint_height_cell(const fort_cell_t *cell, const context_t *context);

FT_INTERNAL
void set_cell_type(fort_cell_t *cell, enum CellType type);

FT_INTERNAL
enum CellType get_cell_type(const fort_cell_t *cell);

FT_INTERNAL
int cell_printf(fort_cell_t *cell, size_t row, char *buf, size_t buf_len, const context_t *context);

FT_INTERNAL
fort_status_t fill_cell_from_string(fort_cell_t *cell, const char *str);

#ifdef FT_HAVE_WCHAR
FT_INTERNAL
int cell_wprintf(fort_cell_t *cell, size_t row, wchar_t *buf, size_t buf_len, const context_t *context);

FT_INTERNAL
fort_status_t fill_cell_from_wstring(fort_cell_t *cell, const wchar_t *str);
#endif

FT_INTERNAL
string_buffer_t *cell_get_string_buffer(fort_cell_t *cell);

#endif /* CELL_H */

/********************************************************
   End of file "cell.h"
 ********************************************************/


/********************************************************
   Begin of file "row.h"
 ********************************************************/

#ifndef ROW_H
#define ROW_H

/* #include "fort_utils.h" */ /* Commented by amalgamation script */
#include "fort.h"
#include <stdarg.h>
/* #include "properties.h" */ /* Commented by amalgamation script */
#ifdef FT_HAVE_WCHAR
#include <wchar.h>
#endif

FT_INTERNAL
fort_row_t *create_row(void);

FT_INTERNAL
void destroy_row(fort_row_t *row);

FT_INTERNAL
fort_row_t *copy_row(fort_row_t *row);

FT_INTERNAL
fort_row_t *create_row_from_string(const char *str);

FT_PRINTF_ATTRIBUTE_FORMAT(1, 0)
FT_INTERNAL
fort_row_t *create_row_from_fmt_string(const char  *fmt, va_list *va_args);

FT_INTERNAL
size_t columns_in_row(const fort_row_t *row);

FT_INTERNAL
fort_cell_t *get_cell(fort_row_t *row, size_t col);

FT_INTERNAL
const fort_cell_t *get_cell_c(const fort_row_t *row, size_t col);

FT_INTERNAL
fort_cell_t *get_cell_and_create_if_not_exists(fort_row_t *row, size_t col);

FT_INTERNAL
fort_status_t swap_row(fort_row_t *cur_row, fort_row_t *ins_row, size_t pos);

FT_INTERNAL
size_t group_cell_number(const fort_row_t *row, size_t master_cell_col);

FT_INTERNAL
int get_row_cell_types(const fort_row_t *row, enum CellType *types, size_t types_sz);

FT_INTERNAL
fort_status_t row_set_cell_span(fort_row_t *row, size_t cell_column, size_t hor_span);

FT_INTERNAL
int print_row_separator(char *buffer, size_t buffer_sz,
                        const size_t *col_width_arr, size_t cols,
                        const fort_row_t *upper_row, const fort_row_t *lower_row,
                        enum HorSeparatorPos separatorPos, const separator_t *sep,
                        const context_t *context);

FT_INTERNAL
int snprintf_row(const fort_row_t *row, char *buffer, size_t buf_sz, size_t *col_width_arr, size_t col_width_arr_sz,
                 size_t row_height, const context_t *context);

#ifdef FT_HAVE_WCHAR
FT_INTERNAL
fort_row_t *create_row_from_wstring(const wchar_t *str);

FT_INTERNAL
fort_row_t *create_row_from_fmt_wstring(const wchar_t  *fmt, va_list *va_args);

FT_INTERNAL
int wprint_row_separator(wchar_t *buffer, size_t buffer_sz,
                         const size_t *col_width_arr, size_t cols,
                         const fort_row_t *upper_row, const fort_row_t *lower_row,
                         enum HorSeparatorPos separatorPos, const separator_t *sep,
                         const context_t *context);

FT_INTERNAL
int wsnprintf_row(const fort_row_t *row, wchar_t *buffer, size_t buf_sz, size_t *col_width_arr, size_t col_width_arr_sz,
                  size_t row_height, const context_t *context);
#endif


#endif /* ROW_H */

/********************************************************
   End of file "row.h"
 ********************************************************/


/********************************************************
   Begin of file "table.h"
 ********************************************************/

#ifndef TABLE_H
#define TABLE_H

/* #include "fort_utils.h" */ /* Commented by amalgamation script */

struct ft_table {
    vector_t *rows;
    fort_table_properties_t *properties;
    string_buffer_t *conv_buffer;
    size_t cur_row;
    size_t cur_col;
    vector_t *separators;
};

FT_INTERNAL
separator_t *create_separator(int enabled);

FT_INTERNAL
void destroy_separator(separator_t *sep);

FT_INTERNAL
separator_t *copy_separator(separator_t *sep);

FT_INTERNAL
fort_status_t get_table_sizes(const ft_table_t *table, size_t *rows, size_t *cols);

FT_INTERNAL
fort_row_t *get_row(ft_table_t *table, size_t row);

FT_INTERNAL
const fort_row_t *get_row_c(const ft_table_t *table, size_t row);

FT_INTERNAL
fort_row_t *get_row_and_create_if_not_exists(ft_table_t *table, size_t row);

FT_INTERNAL
string_buffer_t *get_cur_str_buffer_and_create_if_not_exists(ft_table_t *table);


FT_INTERNAL
fort_status_t table_rows_and_cols_geometry(const ft_table_t *table,
        size_t **col_width_arr_p, size_t *col_width_arr_sz,
        size_t **row_height_arr_p, size_t *row_height_arr_sz,
        enum request_geom_type geom);

FT_INTERNAL
fort_status_t table_geometry(const ft_table_t *table, size_t *height, size_t *width);

#endif /* TABLE_H */

/********************************************************
   End of file "table.h"
 ********************************************************/


/********************************************************
   Begin of file "cell.c"
 ********************************************************/

/* #include "cell.h" */ /* Commented by amalgamation script */
/* #include "properties.h" */ /* Commented by amalgamation script */
/* #include "string_buffer.h" */ /* Commented by amalgamation script */
#include <assert.h>

struct fort_cell {
    string_buffer_t *str_buffer;
    enum CellType cell_type;
};

FT_INTERNAL
fort_cell_t *create_cell(void)
{
    fort_cell_t *cell = (fort_cell_t *)F_CALLOC(sizeof(fort_cell_t), 1);
    if (cell == NULL)
        return NULL;
    cell->str_buffer = create_string_buffer(DEFAULT_STR_BUF_SIZE, CharBuf);
    if (cell->str_buffer == NULL) {
        F_FREE(cell);
        return NULL;
    }
    cell->cell_type = CommonCell;
    return cell;
}

FT_INTERNAL
void destroy_cell(fort_cell_t *cell)
{
    if (cell == NULL)
        return;
    destroy_string_buffer(cell->str_buffer);
    F_FREE(cell);
}

FT_INTERNAL
fort_cell_t *copy_cell(fort_cell_t *cell)
{
    assert(cell);

    fort_cell_t *result = create_cell();
    if (result == NULL)
        return NULL;
    destroy_string_buffer(result->str_buffer);
    result->str_buffer = copy_string_buffer(cell->str_buffer);
    if (result->str_buffer == NULL) {
        destroy_cell(result);
        return NULL;
    }
    result->cell_type = cell->cell_type;
    return result;
}

FT_INTERNAL
void set_cell_type(fort_cell_t *cell, enum CellType type)
{
    assert(cell);
    cell->cell_type = type;
}

FT_INTERNAL
enum CellType get_cell_type(const fort_cell_t *cell)
{
    assert(cell);
    return cell->cell_type;
}

FT_INTERNAL
size_t hint_width_cell(const fort_cell_t *cell, const context_t *context, enum request_geom_type geom)
{
    /* todo:
     * 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
     */

    assert(cell);
    assert(context);
    size_t cell_padding_left = get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_LEFT_PADDING);
    size_t cell_padding_right = get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_RIGHT_PADDING);
    size_t result = cell_padding_left + cell_padding_right;
    if (cell->str_buffer && cell->str_buffer->str.data) {
        result += buffer_text_width(cell->str_buffer);
    }
    result = MAX(result, (size_t)get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_MIN_WIDTH));

    if (geom == INTERN_REPR_GEOMETRY) {
        char cell_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
        get_style_tag_for_cell(context->table_properties, context->row, context->column, cell_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
        result += strlen(cell_style_tag);

        char reset_cell_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
        get_reset_style_tag_for_cell(context->table_properties, context->row, context->column, reset_cell_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
        result += strlen(reset_cell_style_tag);

        char content_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
        get_style_tag_for_content(context->table_properties, context->row, context->column, content_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
        result += strlen(content_style_tag);

        char reset_content_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
        get_reset_style_tag_for_content(context->table_properties, context->row, context->column, reset_content_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
        result += strlen(reset_content_style_tag);
    }

    return result;
}

FT_INTERNAL
size_t hint_height_cell(const fort_cell_t *cell, const context_t *context)
{
    assert(cell);
    assert(context);
    size_t cell_padding_top = get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_TOP_PADDING);
    size_t cell_padding_bottom = get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_BOTTOM_PADDING);
    size_t cell_empty_string_height = get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_EMPTY_STR_HEIGHT);
    size_t result = cell_padding_top + cell_padding_bottom;
    if (cell->str_buffer && cell->str_buffer->str.data) {
        size_t text_height = buffer_text_height(cell->str_buffer);
        result += text_height == 0 ? cell_empty_string_height : text_height;
    }
    return result;
}


FT_INTERNAL
int cell_printf(fort_cell_t *cell, size_t row, char *buf, size_t buf_len, const context_t *context)
{
    const char *space_char = " ";
    int (*buffer_printf_)(string_buffer_t *, size_t, char *, size_t, const context_t *, const char *, const char *) = buffer_printf;
    int (*snprint_n_strings_)(char *, size_t, size_t, const char *) = snprint_n_strings;

    if (cell == NULL || buf_len == 0
        || (buf_len <= hint_width_cell(cell, context, VISIBLE_GEOMETRY))) {
        return -1;
    }

    unsigned int cell_padding_top = get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_TOP_PADDING);
    unsigned int cell_padding_left = get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_LEFT_PADDING);
    unsigned int cell_padding_right = get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_RIGHT_PADDING);

    size_t written = 0;
    size_t invisible_written = 0;
    int tmp = 0;

    /* todo: Dirty hack with changing buf_len! need refactoring. */
    /* Also maybe it is better to move all struff with colors to buffers? */
    char cell_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
    get_style_tag_for_cell(context->table_properties, context->row, context->column, cell_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
    buf_len += strlen(cell_style_tag);

    char reset_cell_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
    get_reset_style_tag_for_cell(context->table_properties, context->row, context->column, reset_cell_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
    buf_len += strlen(reset_cell_style_tag);

    char content_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
    get_style_tag_for_content(context->table_properties, context->row, context->column, content_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
    buf_len += strlen(content_style_tag);

    char reset_content_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
    get_reset_style_tag_for_content(context->table_properties, context->row, context->column, reset_content_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
    buf_len += strlen(reset_content_style_tag);

    /*    CELL_STYLE_T   LEFT_PADDING   CONTENT_STYLE_T  CONTENT   RESET_CONTENT_STYLE_T    RIGHT_PADDING   RESET_CELL_STYLE_T
     *  |              |              |                |         |                       |                |                    |
     *        L1                                                                                                    R1
     *                     L2                                                                   R2
     *                                     L3                               R3
     */

    size_t L2 = cell_padding_left;

    size_t R2 = cell_padding_right;
    size_t R3 = strlen(reset_cell_style_tag);

#define TOTAL_WRITTEN (written + invisible_written)
#define RIGHT (cell_padding_right + extra_right)

#define WRITE_CELL_STYLE_TAG        CHCK_RSLT_ADD_TO_INVISIBLE_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, 1, cell_style_tag))
#define WRITE_RESET_CELL_STYLE_TAG  CHCK_RSLT_ADD_TO_INVISIBLE_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, 1, reset_cell_style_tag))
#define WRITE_CONTENT_STYLE_TAG        CHCK_RSLT_ADD_TO_INVISIBLE_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, 1, content_style_tag))
#define WRITE_RESET_CONTENT_STYLE_TAG  CHCK_RSLT_ADD_TO_INVISIBLE_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, 1, reset_content_style_tag))

    if (row >= hint_height_cell(cell, context)
        || row < cell_padding_top
        || row >= (cell_padding_top + buffer_text_height(cell->str_buffer))) {
        WRITE_CELL_STYLE_TAG;
        WRITE_CONTENT_STYLE_TAG;
        WRITE_RESET_CONTENT_STYLE_TAG;
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len, buf_len - 1 - TOTAL_WRITTEN - R3, space_char));
        WRITE_RESET_CELL_STYLE_TAG;
        return (int)TOTAL_WRITTEN;
    }

    WRITE_CELL_STYLE_TAG;
    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, L2, space_char));
    if (cell->str_buffer) {
        CHCK_RSLT_ADD_TO_WRITTEN(buffer_printf_(cell->str_buffer, row - cell_padding_top, buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN - R2 - R3, context, content_style_tag, reset_content_style_tag));
    } else {
        WRITE_CONTENT_STYLE_TAG;
        WRITE_RESET_CONTENT_STYLE_TAG;
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN - R2 - R3, space_char));
    }
    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, R2, space_char));
    WRITE_RESET_CELL_STYLE_TAG;

    return (int)TOTAL_WRITTEN;

clear:
    return -1;
#undef WRITE_CELL_STYLE_TAG
#undef WRITE_RESET_CELL_STYLE_TAG
#undef WRITE_CONTENT_STYLE_TAG
#undef WRITE_RESET_CONTENT_STYLE_TAG
#undef TOTAL_WRITTEN
#undef RIGHT
}

#ifdef FT_HAVE_WCHAR
FT_INTERNAL
int cell_wprintf(fort_cell_t *cell, size_t row, wchar_t *buf, size_t buf_len, const context_t *context)
{
    const char *space_char = " ";
    int (*buffer_printf_)(string_buffer_t *, size_t, wchar_t *, size_t, const context_t *, const char *, const char *) = buffer_wprintf;
    int (*snprint_n_strings_)(wchar_t *, size_t, size_t, const char *) = wsnprint_n_string;

    if (cell == NULL || buf_len == 0
        || (buf_len <= hint_width_cell(cell, context, VISIBLE_GEOMETRY))) {
        return -1;
    }

    unsigned int cell_padding_top = get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_TOP_PADDING);
    unsigned int cell_padding_left = get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_LEFT_PADDING);
    unsigned int cell_padding_right = get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_RIGHT_PADDING);

    size_t written = 0;
    size_t invisible_written = 0;
    int tmp = 0;

    /* todo: Dirty hack with changing buf_len! need refactoring. */
    /* Also maybe it is better to move all struff with colors to buffers? */
    char cell_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
    get_style_tag_for_cell(context->table_properties, context->row, context->column, cell_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
    buf_len += strlen(cell_style_tag);

    char reset_cell_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
    get_reset_style_tag_for_cell(context->table_properties, context->row, context->column, reset_cell_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
    buf_len += strlen(reset_cell_style_tag);

    char content_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
    get_style_tag_for_content(context->table_properties, context->row, context->column, content_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
    buf_len += strlen(content_style_tag);

    char reset_content_style_tag[TEXT_STYLE_TAG_MAX_SIZE];
    get_reset_style_tag_for_content(context->table_properties, context->row, context->column, reset_content_style_tag, TEXT_STYLE_TAG_MAX_SIZE);
    buf_len += strlen(reset_content_style_tag);

    /*    CELL_STYLE_T   LEFT_PADDING   CONTENT_STYLE_T  CONTENT   RESET_CONTENT_STYLE_T    RIGHT_PADDING   RESET_CELL_STYLE_T
     *  |              |              |                |         |                       |                |                    |
     *        L1                                                                                                    R1
     *                     L2                                                                   R2
     *                                     L3                               R3
     */

    size_t L2 = cell_padding_left;

    size_t R2 = cell_padding_right;
    size_t R3 = strlen(reset_cell_style_tag);

#define TOTAL_WRITTEN (written + invisible_written)
#define RIGHT (right + extra_right)

#define WRITE_CELL_STYLE_TAG        CHCK_RSLT_ADD_TO_INVISIBLE_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, 1, cell_style_tag))
#define WRITE_RESET_CELL_STYLE_TAG  CHCK_RSLT_ADD_TO_INVISIBLE_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, 1, reset_cell_style_tag))
#define WRITE_CONTENT_STYLE_TAG        CHCK_RSLT_ADD_TO_INVISIBLE_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, 1, content_style_tag))
#define WRITE_RESET_CONTENT_STYLE_TAG  CHCK_RSLT_ADD_TO_INVISIBLE_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, 1, reset_content_style_tag))

    if (row >= hint_height_cell(cell, context)
        || row < cell_padding_top
        || row >= (cell_padding_top + buffer_text_height(cell->str_buffer))) {
        WRITE_CELL_STYLE_TAG;
        WRITE_CONTENT_STYLE_TAG;
        WRITE_RESET_CONTENT_STYLE_TAG;
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len, buf_len - 1 - TOTAL_WRITTEN - R3, space_char));
        WRITE_RESET_CELL_STYLE_TAG;
        return (int)TOTAL_WRITTEN;
    }

    WRITE_CELL_STYLE_TAG;
    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, L2, space_char));
    if (cell->str_buffer) {
        CHCK_RSLT_ADD_TO_WRITTEN(buffer_printf_(cell->str_buffer, row - cell_padding_top, buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN - R2 - R3, context, content_style_tag, reset_content_style_tag));
    } else {
        WRITE_CONTENT_STYLE_TAG;
        WRITE_RESET_CONTENT_STYLE_TAG;
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN - R2 - R3, space_char));
    }
    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buf + TOTAL_WRITTEN, buf_len - TOTAL_WRITTEN, R2, space_char));
    WRITE_RESET_CELL_STYLE_TAG;

    return (int)TOTAL_WRITTEN;

clear:
    return -1;
#undef WRITE_CELL_STYLE_TAG
#undef WRITE_RESET_CELL_STYLE_TAG
#undef WRITE_CONTENT_STYLE_TAG
#undef WRITE_RESET_CONTENT_STYLE_TAG
#undef TOTAL_WRITTEN
#undef RIGHT
}
#endif

FT_INTERNAL
fort_status_t fill_cell_from_string(fort_cell_t *cell, const char *str)
{
    assert(str);
    assert(cell);

    return fill_buffer_from_string(cell->str_buffer, str);
}

#ifdef FT_HAVE_WCHAR
FT_INTERNAL
fort_status_t fill_cell_from_wstring(fort_cell_t *cell, const wchar_t *str)
{
    assert(str);
    assert(cell);

    return fill_buffer_from_wstring(cell->str_buffer, str);
}

#endif

FT_INTERNAL
string_buffer_t *cell_get_string_buffer(fort_cell_t *cell)
{
    assert(cell);
    assert(cell->str_buffer);
    return cell->str_buffer;
}


/********************************************************
   End of file "cell.c"
 ********************************************************/


/********************************************************
   Begin of file "fort_impl.c"
 ********************************************************/

/*
libfort

MIT License

Copyright (c) 2017 - 2018 Seleznev Anton

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include "fort.h"
#include <assert.h>
#include <string.h>
#include <wchar.h>
#include <ctype.h>

/* #include "vector.h" */ /* Commented by amalgamation script */
/* #include "fort_utils.h" */ /* Commented by amalgamation script */
/* #include "string_buffer.h" */ /* Commented by amalgamation script */
/* #include "table.h" */ /* Commented by amalgamation script */
/* #include "row.h" */ /* Commented by amalgamation script */
/* #include "properties.h" */ /* Commented by amalgamation script */


ft_table_t *ft_create_table(void)
{
    ft_table_t *result = (ft_table_t *)F_CALLOC(1, sizeof(ft_table_t));
    if (result == NULL)
        return NULL;

    result->rows = create_vector(sizeof(fort_row_t *), DEFAULT_VECTOR_CAPACITY);
    if (result->rows == NULL) {
        F_FREE(result);
        return NULL;
    }
    result->separators = create_vector(sizeof(separator_t *), DEFAULT_VECTOR_CAPACITY);
    if (result->separators == NULL) {
        destroy_vector(result->rows);
        F_FREE(result);
        return NULL;
    }
    result->properties = NULL;
    result->conv_buffer = NULL;
    result->cur_row = 0;
    result->cur_col = 0;
    return result;
}


void ft_destroy_table(ft_table_t *table)
{
    size_t i = 0;

    if (table == NULL)
        return;

    if (table->rows) {
        size_t row_n = vector_size(table->rows);
        for (i = 0; i < row_n; ++i) {
            destroy_row(*(fort_row_t **)vector_at(table->rows, i));
        }
        destroy_vector(table->rows);
    }
    if (table->separators) {
        size_t row_n = vector_size(table->separators);
        for (i = 0; i < row_n; ++i) {
            destroy_separator(*(separator_t **)vector_at(table->separators, i));
        }
        destroy_vector(table->separators);
    }
    destroy_table_properties(table->properties);
    destroy_string_buffer(table->conv_buffer);
    F_FREE(table);
}

ft_table_t *ft_copy_table(ft_table_t *table)
{
    if (table == NULL)
        return NULL;

    ft_table_t *result = ft_create_table();
    if (result == NULL)
        return NULL;

    size_t i = 0;
    size_t rows_n = vector_size(table->rows);
    for (i = 0; i < rows_n; ++i) {
        fort_row_t *row = *(fort_row_t **)vector_at(table->rows, i);
        fort_row_t *new_row = copy_row(row);
        if (new_row == NULL) {
            ft_destroy_table(result);
            return NULL;
        }
        vector_push(result->rows, &new_row);
    }

    size_t sep_sz = vector_size(table->separators);
    for (i = 0; i < sep_sz; ++i) {
        separator_t *sep = *(separator_t **)vector_at(table->separators, i);
        separator_t *new_sep = copy_separator(sep);
        if (new_sep == NULL) {
            ft_destroy_table(result);
            return NULL;
        }
        vector_push(result->separators, &new_sep);
    }


    result->properties = copy_table_properties(table->properties);
    if (result->properties == NULL) {
        ft_destroy_table(result);
        return NULL;
    }

    /* todo: copy conv_buffer  ??  */

    result->cur_row = table->cur_row;
    result->cur_col = table->cur_col;
    return result;
}


void ft_ln(ft_table_t *table)
{
    assert(table);
    table->cur_col = 0;
    table->cur_row++;
}

size_t ft_cur_row(ft_table_t *table)
{
    assert(table);
    return table->cur_row;
}

size_t ft_cur_col(ft_table_t *table)
{
    assert(table);
    return table->cur_col;
}

void ft_set_cur_cell(ft_table_t *table, size_t row, size_t col)
{
    assert(table);
    table->cur_row = row;
    table->cur_col = col;
}

FT_PRINTF_ATTRIBUTE_FORMAT(3, 0)
static int ft_row_printf_impl(ft_table_t *table, size_t row, const char *fmt, va_list *va)
{
#define CREATE_ROW_FROM_FMT_STRING create_row_from_fmt_string
    size_t i = 0;
    size_t new_cols = 0;

    if (table == NULL)
        return -1;

    fort_row_t *new_row = CREATE_ROW_FROM_FMT_STRING(fmt, va);

    if (new_row == NULL) {
        return -1;
    }

    fort_row_t **cur_row_p = NULL;
    size_t sz = vector_size(table->rows);
    if (row >= sz) {
        size_t push_n = row - sz + 1;
        for (i = 0; i < push_n; ++i) {
            fort_row_t *padding_row = create_row();
            if (padding_row == NULL)
                goto clear;

            if (FT_IS_ERROR(vector_push(table->rows, &padding_row))) {
                destroy_row(padding_row);
                goto clear;
            }
        }
    }
    /* todo: clearing pushed items in case of error ?? */

    new_cols = columns_in_row(new_row);
    cur_row_p = (fort_row_t **)vector_at(table->rows, row);
    swap_row(*cur_row_p, new_row, table->cur_col);

    table->cur_col += new_cols;
    destroy_row(new_row);
    return (int)new_cols;

clear:
    destroy_row(new_row);
    return -1;
#undef CREATE_ROW_FROM_FMT_STRING
}

#ifdef FT_HAVE_WCHAR
static int ft_row_wprintf_impl(ft_table_t *table, size_t row, const wchar_t *fmt, va_list *va)
{
#define CREATE_ROW_FROM_FMT_STRING create_row_from_fmt_wstring
    size_t i = 0;
    size_t new_cols = 0;

    if (table == NULL)
        return -1;

    fort_row_t *new_row = CREATE_ROW_FROM_FMT_STRING(fmt, va);

    if (new_row == NULL) {
        return -1;
    }

    fort_row_t **cur_row_p = NULL;
    size_t sz = vector_size(table->rows);
    if (row >= sz) {
        size_t push_n = row - sz + 1;
        for (i = 0; i < push_n; ++i) {
            fort_row_t *padding_row = create_row();
            if (padding_row == NULL)
                goto clear;

            if (FT_IS_ERROR(vector_push(table->rows, &padding_row))) {
                destroy_row(padding_row);
                goto clear;
            }
        }
    }
    /* todo: clearing pushed items in case of error ?? */

    new_cols = columns_in_row(new_row);
    cur_row_p = (fort_row_t **)vector_at(table->rows, row);
    swap_row(*cur_row_p, new_row, table->cur_col);

    table->cur_col += new_cols;
    destroy_row(new_row);
    return (int)new_cols;

clear:
    destroy_row(new_row);
    return -1;
#undef CREATE_ROW_FROM_FMT_STRING
}
#endif

#if defined(FT_CLANG_COMPILER) || defined(FT_GCC_COMPILER)
#define FT_PRINTF ft_printf
#define FT_PRINTF_LN ft_printf_ln
#else
#define FT_PRINTF ft_printf_impl
#define FT_PRINTF_LN ft_printf_ln_impl
#endif



int FT_PRINTF(ft_table_t *table, const char *fmt, ...)
{
    assert(table);
    va_list va;
    va_start(va, fmt);
    int result = ft_row_printf_impl(table, table->cur_row, fmt, &va);
    va_end(va);
    return result;
}

int FT_PRINTF_LN(ft_table_t *table, const char *fmt, ...)
{
    assert(table);
    va_list va;
    va_start(va, fmt);
    int result = ft_row_printf_impl(table, table->cur_row, fmt, &va);
    if (result >= 0) {
        ft_ln(table);
    }
    va_end(va);
    return result;
}

#undef FT_PRINTF
#undef FT_PRINTF_LN
#undef FT_HDR_PRINTF
#undef FT_HDR_PRINTF_LN

#ifdef FT_HAVE_WCHAR
int ft_wprintf(ft_table_t *table, const wchar_t *fmt, ...)
{
    assert(table);
    va_list va;
    va_start(va, fmt);
    int result = ft_row_wprintf_impl(table, table->cur_row, fmt, &va);
    va_end(va);
    return result;
}

int ft_wprintf_ln(ft_table_t *table, const wchar_t *fmt, ...)
{
    assert(table);
    va_list va;
    va_start(va, fmt);
    int result = ft_row_wprintf_impl(table, table->cur_row, fmt, &va);
    if (result >= 0) {
        ft_ln(table);
    }
    va_end(va);
    return result;
}

#endif

void ft_set_default_printf_field_separator(char separator)
{
    g_col_separator = separator;
}


static int ft_write_impl(ft_table_t *table, const char *cell_content)
{
    assert(table);
    string_buffer_t *str_buffer = get_cur_str_buffer_and_create_if_not_exists(table);
    if (str_buffer == NULL)
        return FT_ERROR;

    int status = fill_buffer_from_string(str_buffer, cell_content);
    if (FT_IS_SUCCESS(status)) {
        table->cur_col++;
    }
    return status;
}


#ifdef FT_HAVE_WCHAR

static int ft_wwrite_impl(ft_table_t *table, const wchar_t *cell_content)
{
    assert(table);
    string_buffer_t *str_buffer = get_cur_str_buffer_and_create_if_not_exists(table);
    if (str_buffer == NULL)
        return FT_ERROR;

    int status = fill_buffer_from_wstring(str_buffer, cell_content);
    if (FT_IS_SUCCESS(status)) {
        table->cur_col++;
    }
    return status;
}

#endif


int ft_nwrite(ft_table_t *table, size_t count, const char *cell_content, ...)
{
    size_t i = 0;
    assert(table);
    int status = ft_write_impl(table, cell_content);
    if (FT_IS_ERROR(status))
        return status;

    va_list va;
    va_start(va, cell_content);
    --count;
    for (i = 0; i < count; ++i) {
        const char *cell = va_arg(va, const char *);
        status = ft_write_impl(table, cell);
        if (FT_IS_ERROR(status)) {
            va_end(va);
            return status;
        }
    }
    va_end(va);
    return status;
}

int ft_nwrite_ln(ft_table_t *table, size_t count, const char *cell_content, ...)
{
    size_t i = 0;
    assert(table);
    int status = ft_write_impl(table, cell_content);
    if (FT_IS_ERROR(status))
        return status;

    va_list va;
    va_start(va, cell_content);
    --count;
    for (i = 0; i < count; ++i) {
        const char *cell = va_arg(va, const char *);
        status = ft_write_impl(table, cell);
        if (FT_IS_ERROR(status)) {
            va_end(va);
            return status;
        }
    }
    va_end(va);

    ft_ln(table);
    return status;
}

#ifdef FT_HAVE_WCHAR

int ft_nwwrite(ft_table_t *table, size_t n, const wchar_t *cell_content, ...)
{
    size_t i = 0;
    assert(table);
    int status = ft_wwrite_impl(table, cell_content);
    if (FT_IS_ERROR(status))
        return status;

    va_list va;
    va_start(va, cell_content);
    --n;
    for (i = 0; i < n; ++i) {
        const wchar_t *cell = va_arg(va, const wchar_t *);
        status = ft_wwrite_impl(table, cell);
        if (FT_IS_ERROR(status)) {
            va_end(va);
            return status;
        }
    }
    va_end(va);
    return status;
}

int ft_nwwrite_ln(ft_table_t *table, size_t n, const wchar_t *cell_content, ...)
{
    size_t i = 0;
    assert(table);
    int status = ft_wwrite_impl(table, cell_content);
    if (FT_IS_ERROR(status))
        return status;

    va_list va;
    va_start(va, cell_content);
    --n;
    for (i = 0; i < n; ++i) {
        const wchar_t *cell = va_arg(va, const wchar_t *);
        status = ft_wwrite_impl(table, cell);
        if (FT_IS_ERROR(status)) {
            va_end(va);
            return status;
        }
    }
    va_end(va);

    ft_ln(table);
    return status;
}
#endif


int ft_row_write(ft_table_t *table, size_t cols, const char *cells[])
{
    size_t i = 0;
    assert(table);
    for (i = 0; i < cols; ++i) {
        int status = ft_write_impl(table, cells[i]);
        if (FT_IS_ERROR(status)) {
            /* todo: maybe current pos in case of error should be equal to the one before function call? */
            return status;
        }
    }
    return FT_SUCCESS;
}

int ft_row_write_ln(ft_table_t *table, size_t cols, const char *cells[])
{
    assert(table);
    int status = ft_row_write(table, cols, cells);
    if (FT_IS_SUCCESS(status)) {
        ft_ln(table);
    }
    return status;
}

#ifdef FT_HAVE_WCHAR
int ft_row_wwrite(ft_table_t *table, size_t cols, const wchar_t *cells[])
{
    size_t i = 0;
    assert(table);
    for (i = 0; i < cols; ++i) {
        int status = ft_wwrite_impl(table, cells[i]);
        if (FT_IS_ERROR(status)) {
            /* todo: maybe current pos in case of error should be equal
             * to the one before function call?
             */
            return status;
        }
    }
    return FT_SUCCESS;
}

int ft_row_wwrite_ln(ft_table_t *table, size_t cols, const wchar_t *cells[])
{
    assert(table);
    int status = ft_row_wwrite(table, cols, cells);
    if (FT_IS_SUCCESS(status)) {
        ft_ln(table);
    }
    return status;
}
#endif



int ft_table_write(ft_table_t *table, size_t rows, size_t cols, const char *table_cells[])
{
    size_t i = 0;
    assert(table);
    for (i = 0; i < rows; ++i) {
        int status = ft_row_write(table, cols, (const char **)&table_cells[i * cols]);
        if (FT_IS_ERROR(status)) {
            /* todo: maybe current pos in case of error should be equal
             * to the one before function call?
             */
            return status;
        }
        if (i != rows - 1)
            ft_ln(table);
    }
    return FT_SUCCESS;
}

int ft_table_write_ln(ft_table_t *table, size_t rows, size_t cols, const char *table_cells[])
{
    assert(table);
    int status = ft_table_write(table, rows, cols, table_cells);
    if (FT_IS_SUCCESS(status)) {
        ft_ln(table);
    }
    return status;
}


#ifdef FT_HAVE_WCHAR
int ft_table_wwrite(ft_table_t *table, size_t rows, size_t cols, const wchar_t *table_cells[])
{
    size_t i = 0;
    assert(table);
    for (i = 0; i < rows; ++i) {
        int status = ft_row_wwrite(table, cols, (const wchar_t **)&table_cells[i * cols]);
        if (FT_IS_ERROR(status)) {
            /* todo: maybe current pos in case of error should be equal
             * to the one before function call?
             */
            return status;
        }
        if (i != rows - 1)
            ft_ln(table);
    }
    return FT_SUCCESS;
}

int ft_table_wwrite_ln(ft_table_t *table, size_t rows, size_t cols, const wchar_t *table_cells[])
{
    assert(table);
    int status = ft_table_wwrite(table, rows, cols, table_cells);
    if (FT_IS_SUCCESS(status)) {
        ft_ln(table);
    }
    return status;
}
#endif



const char *ft_to_string(const ft_table_t *table)
{
    typedef char char_type;
    const enum str_buf_type buf_type = CharBuf;
    const char *space_char = " ";
    const char *new_line_char = "\n";
#define EMPTY_STRING ""
    int (*snprintf_row_)(const fort_row_t *, char *, size_t, size_t *, size_t, size_t, const context_t *) = snprintf_row;
    int (*print_row_separator_)(char *, size_t,
                                const size_t *, size_t,
                                const fort_row_t *, const fort_row_t *,
                                enum HorSeparatorPos, const separator_t *,
                                const context_t *) = print_row_separator;
    int (*snprint_n_strings_)(char *, size_t, size_t, const char *) = snprint_n_strings;
    assert(table);

    /* Determining size of table string representation */
    size_t height = 0;
    size_t width = 0;
    int status = table_geometry(table, &height, &width);
    if (FT_IS_ERROR(status)) {
        return NULL;
    }
    size_t sz = height * width + 1;

    /* Allocate string buffer for string representation */
    if (table->conv_buffer == NULL) {
        ((ft_table_t *)table)->conv_buffer = create_string_buffer(sz, buf_type);
        if (table->conv_buffer == NULL)
            return NULL;
    }
    while (string_buffer_capacity(table->conv_buffer) < sz) {
        if (FT_IS_ERROR(realloc_string_buffer_without_copy(table->conv_buffer))) {
            return NULL;
        }
    }
    char_type *buffer = (char_type *)buffer_get_data(table->conv_buffer);


    size_t cols = 0;
    size_t rows = 0;
    size_t *col_width_arr = NULL;
    size_t *row_height_arr = NULL;
    status = table_rows_and_cols_geometry(table, &col_width_arr, &cols, &row_height_arr, &rows, VISIBLE_GEOMETRY);
    if (FT_IS_ERROR(status))
        return NULL;

    if (rows == 0)
        return EMPTY_STRING;

    size_t written = 0;
    int tmp = 0;
    size_t i = 0;
    context_t context;
    context.table_properties = (table->properties ? table->properties : &g_table_properties);
    fort_row_t *prev_row = NULL;
    fort_row_t *cur_row = NULL;
    separator_t *cur_sep = NULL;
    size_t sep_size = vector_size(table->separators);

    /* Print top margin */
    for (i = 0; i < context.table_properties->entire_table_properties.top_margin; ++i) {
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, sz - written, width - 1/* minus new_line*/, space_char));
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, sz - written, 1, new_line_char));
    }

    for (i = 0; i < rows; ++i) {
        cur_sep = (i < sep_size) ? (*(separator_t **)vector_at(table->separators, i)) : NULL;
        cur_row = *(fort_row_t **)vector_at(table->rows, i);
        enum HorSeparatorPos separatorPos = (i == 0) ? TopSeparator : InsideSeparator;
        context.row = i;
        CHCK_RSLT_ADD_TO_WRITTEN(print_row_separator_(buffer + written, sz - written, col_width_arr, cols, prev_row, cur_row, separatorPos, cur_sep, &context));
        CHCK_RSLT_ADD_TO_WRITTEN(snprintf_row_(cur_row, buffer + written, sz - written, col_width_arr, cols, row_height_arr[i], &context));
        prev_row = cur_row;
    }
    cur_row = NULL;
    cur_sep = (i < sep_size) ? (*(separator_t **)vector_at(table->separators, i)) : NULL;
    CHCK_RSLT_ADD_TO_WRITTEN(print_row_separator_(buffer + written, sz - written, col_width_arr, cols, prev_row, cur_row, BottomSeparator, cur_sep, &context));

    /* Print bottom margin */
    for (i = 0; i < context.table_properties->entire_table_properties.bottom_margin; ++i) {
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, sz - written, width - 1/* minus new_line*/, space_char));
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, sz - written, 1, new_line_char));
    }


    F_FREE(col_width_arr);
    F_FREE(row_height_arr);
    return buffer;

clear:
    F_FREE(col_width_arr);
    F_FREE(row_height_arr);
    return NULL;
#undef EMPTY_STRING
}


#ifdef FT_HAVE_WCHAR

const wchar_t *ft_to_wstring(const ft_table_t *table)
{
    typedef wchar_t char_type;
    const enum str_buf_type buf_type = WCharBuf;
    const char *space_char = " ";
    const char *new_line_char = "\n";
#define EMPTY_STRING L""
    int (*snprintf_row_)(const fort_row_t *, wchar_t *, size_t, size_t *, size_t, size_t, const context_t *) = wsnprintf_row;
    int (*print_row_separator_)(wchar_t *, size_t,
                                const size_t *, size_t,
                                const fort_row_t *, const fort_row_t *,
                                enum HorSeparatorPos, const separator_t *,
                                const context_t *) = wprint_row_separator;
    int (*snprint_n_strings_)(wchar_t *, size_t, size_t, const char *) = wsnprint_n_string;


    assert(table);

    /* Determining size of table string representation */
    size_t height = 0;
    size_t width = 0;
    int status = table_geometry(table, &height, &width);
    if (FT_IS_ERROR(status)) {
        return NULL;
    }
    size_t sz = height * width + 1;

    /* Allocate string buffer for string representation */
    if (table->conv_buffer == NULL) {
        ((ft_table_t *)table)->conv_buffer = create_string_buffer(sz, buf_type);
        if (table->conv_buffer == NULL)
            return NULL;
    }
    while (string_buffer_capacity(table->conv_buffer) < sz) {
        if (FT_IS_ERROR(realloc_string_buffer_without_copy(table->conv_buffer))) {
            return NULL;
        }
    }
    char_type *buffer = (char_type *)buffer_get_data(table->conv_buffer);


    size_t cols = 0;
    size_t rows = 0;
    size_t *col_width_arr = NULL;
    size_t *row_height_arr = NULL;
    status = table_rows_and_cols_geometry(table, &col_width_arr, &cols, &row_height_arr, &rows, VISIBLE_GEOMETRY);

    if (rows == 0)
        return EMPTY_STRING;

    if (FT_IS_ERROR(status))
        return NULL;

    size_t written = 0;
    int tmp = 0;
    size_t i = 0;
    context_t context;
    context.table_properties = (table->properties ? table->properties : &g_table_properties);
    fort_row_t *prev_row = NULL;
    fort_row_t *cur_row = NULL;
    separator_t *cur_sep = NULL;
    size_t sep_size = vector_size(table->separators);

    /* Print top margin */
    for (i = 0; i < context.table_properties->entire_table_properties.top_margin; ++i) {
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, sz - written, width - 1/* minus new_line*/, space_char));
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, sz - written, 1, new_line_char));
    }

    for (i = 0; i < rows; ++i) {
        cur_sep = (i < sep_size) ? (*(separator_t **)vector_at(table->separators, i)) : NULL;
        cur_row = *(fort_row_t **)vector_at(table->rows, i);
        enum HorSeparatorPos separatorPos = (i == 0) ? TopSeparator : InsideSeparator;
        context.row = i;
        CHCK_RSLT_ADD_TO_WRITTEN(print_row_separator_(buffer + written, sz - written, col_width_arr, cols, prev_row, cur_row, separatorPos, cur_sep, &context));
        CHCK_RSLT_ADD_TO_WRITTEN(snprintf_row_(cur_row, buffer + written, sz - written, col_width_arr, cols, row_height_arr[i], &context));
        prev_row = cur_row;
    }
    cur_row = NULL;
    cur_sep = (i < sep_size) ? (*(separator_t **)vector_at(table->separators, i)) : NULL;
    CHCK_RSLT_ADD_TO_WRITTEN(print_row_separator_(buffer + written, sz - written, col_width_arr, cols, prev_row, cur_row, BottomSeparator, cur_sep, &context));

    /* Print bottom margin */
    for (i = 0; i < context.table_properties->entire_table_properties.bottom_margin; ++i) {
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, sz - written, width - 1/* minus new_line*/, space_char));
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, sz - written, 1, new_line_char));
    }

    F_FREE(col_width_arr);
    F_FREE(row_height_arr);
    return buffer;

clear:
    F_FREE(col_width_arr);
    F_FREE(row_height_arr);
    return NULL;
#undef EMPTY_STRING
}

#endif


int ft_add_separator(ft_table_t *table)
{
    assert(table);
    assert(table->separators);

    while (vector_size(table->separators) <= table->cur_row) {
        separator_t *sep_p = create_separator(F_FALSE);
        if (sep_p == NULL)
            return FT_MEMORY_ERROR;
        int status = vector_push(table->separators, &sep_p);
        if (FT_IS_ERROR(status))
            return status;
    }

    separator_t **sep_p = (separator_t **)vector_at(table->separators, table->cur_row);
    if (*sep_p == NULL)
        *sep_p = create_separator(F_TRUE);
    else
        (*sep_p)->enabled = F_TRUE;

    if (*sep_p == NULL)
        return FT_ERROR;
    return FT_SUCCESS;
}

static const struct fort_border_style *built_in_styles[] = {
    &FORT_BASIC_STYLE,
    &FORT_BASIC2_STYLE,
    &FORT_SIMPLE_STYLE,
    &FORT_PLAIN_STYLE,
    &FORT_DOT_STYLE,
    &FORT_EMPTY_STYLE,
    &FORT_EMPTY2_STYLE,
    &FORT_SOLID_STYLE,
    &FORT_SOLID_ROUND_STYLE,
    &FORT_NICE_STYLE,
    &FORT_DOUBLE_STYLE,
    &FORT_DOUBLE2_STYLE,
    &FORT_BOLD_STYLE,
    &FORT_BOLD2_STYLE,
    &FORT_FRAME_STYLE,
};
#define BUILT_IN_STYLES_SZ (sizeof(built_in_styles) / sizeof(built_in_styles[0]))

/* todo: remove this stupid and dangerous code */
static const struct ft_border_style built_in_external_styles[BUILT_IN_STYLES_SZ] = {
    {
        {"", "", "", "", "", ""},
        {"", "", "", "", "", ""},
        ""
    }
};

const struct ft_border_style *const FT_BASIC_STYLE = &built_in_external_styles[0];
const struct ft_border_style *const FT_BASIC2_STYLE = &built_in_external_styles[1];
const struct ft_border_style *const FT_SIMPLE_STYLE = &built_in_external_styles[2];
const struct ft_border_style *const FT_PLAIN_STYLE = &built_in_external_styles[3];
const struct ft_border_style *const FT_DOT_STYLE = &built_in_external_styles[4];
const struct ft_border_style *const FT_EMPTY_STYLE  = &built_in_external_styles[5];
const struct ft_border_style *const FT_EMPTY2_STYLE  = &built_in_external_styles[6];
const struct ft_border_style *const FT_SOLID_STYLE  = &built_in_external_styles[7];
const struct ft_border_style *const FT_SOLID_ROUND_STYLE  = &built_in_external_styles[8];
const struct ft_border_style *const FT_NICE_STYLE  = &built_in_external_styles[9];
const struct ft_border_style *const FT_DOUBLE_STYLE  = &built_in_external_styles[10];
const struct ft_border_style *const FT_DOUBLE2_STYLE  = &built_in_external_styles[11];
const struct ft_border_style *const FT_BOLD_STYLE  = &built_in_external_styles[12];
const struct ft_border_style *const FT_BOLD2_STYLE  = &built_in_external_styles[13];
const struct ft_border_style *const FT_FRAME_STYLE  = &built_in_external_styles[14];

static void set_border_props_for_props(fort_table_properties_t *properties, const struct ft_border_style *style)
{
    if (style >= built_in_external_styles && style < (built_in_external_styles + BUILT_IN_STYLES_SZ)) {
        size_t pos = (size_t)(style - built_in_external_styles);
        memcpy(&(properties->border_style), built_in_styles[pos], sizeof(struct fort_border_style));
        return;
    }

    const struct ft_border_chars *border_chs = &(style->border_chs);
    const struct ft_border_chars *header_border_chs = &(style->header_border_chs);

#define BOR_CHARS properties->border_style.border_chars
#define H_BOR_CHARS properties->border_style.header_border_chars
#define SEP_CHARS properties->border_style.separator_chars

    BOR_CHARS[TT_bip] = border_chs->top_border_ch;
    BOR_CHARS[IH_bip] = border_chs->separator_ch;
    BOR_CHARS[BB_bip] = border_chs->bottom_border_ch;
    BOR_CHARS[LL_bip] = BOR_CHARS[IV_bip] = BOR_CHARS[RR_bip] = border_chs->side_border_ch;

    BOR_CHARS[TL_bip] = BOR_CHARS[TV_bip] = BOR_CHARS[TR_bip] = border_chs->out_intersect_ch;
    BOR_CHARS[LH_bip] = BOR_CHARS[RH_bip] = border_chs->out_intersect_ch;
    BOR_CHARS[BL_bip] = BOR_CHARS[BV_bip] = BOR_CHARS[BR_bip] = border_chs->out_intersect_ch;
    BOR_CHARS[II_bip] = border_chs->in_intersect_ch;

    BOR_CHARS[LI_bip] = BOR_CHARS[TI_bip] = BOR_CHARS[RI_bip] = BOR_CHARS[BI_bip] = border_chs->in_intersect_ch;

    if (strlen(border_chs->separator_ch) == 0 && strlen(border_chs->in_intersect_ch) == 0) {
        BOR_CHARS[LH_bip] = BOR_CHARS[RH_bip] = "\0";
    }

    H_BOR_CHARS[TT_bip] = header_border_chs->top_border_ch;
    H_BOR_CHARS[IH_bip] = header_border_chs->separator_ch;
    H_BOR_CHARS[BB_bip] = header_border_chs->bottom_border_ch;
    H_BOR_CHARS[LL_bip] = H_BOR_CHARS[IV_bip] = H_BOR_CHARS[RR_bip] = header_border_chs->side_border_ch;

    H_BOR_CHARS[TL_bip] = H_BOR_CHARS[TV_bip] = H_BOR_CHARS[TR_bip] = header_border_chs->out_intersect_ch;
    H_BOR_CHARS[LH_bip] = H_BOR_CHARS[RH_bip] = header_border_chs->out_intersect_ch;
    H_BOR_CHARS[BL_bip] = H_BOR_CHARS[BV_bip] = H_BOR_CHARS[BR_bip] = header_border_chs->out_intersect_ch;
    H_BOR_CHARS[II_bip] = header_border_chs->in_intersect_ch;

    H_BOR_CHARS[LI_bip] = H_BOR_CHARS[TI_bip] = H_BOR_CHARS[RI_bip] = H_BOR_CHARS[BI_bip] = header_border_chs->in_intersect_ch;

    if (strlen(header_border_chs->separator_ch) == 0 && strlen(header_border_chs->in_intersect_ch) == 0) {
        BOR_CHARS[LH_bip] = BOR_CHARS[RH_bip] = "\0";
    }

    SEP_CHARS[LH_sip] = SEP_CHARS[RH_sip] = SEP_CHARS[II_sip] = header_border_chs->out_intersect_ch;
    SEP_CHARS[TI_sip] = SEP_CHARS[BI_sip] = header_border_chs->out_intersect_ch;
    SEP_CHARS[IH_sip] = style->hor_separator_char;


#undef BOR_CHARS
#undef H_BOR_CHARS
#undef SEP_CHARS
}


int ft_set_default_border_style(const struct ft_border_style *style)
{
    set_border_props_for_props(&g_table_properties, style);
    return FT_SUCCESS;
}

int ft_set_border_style(ft_table_t *table, const struct ft_border_style *style)
{
    assert(table);
    if (table->properties == NULL) {
        table->properties = create_table_properties();
        if (table->properties == NULL)
            return FT_MEMORY_ERROR;
    }
    set_border_props_for_props(table->properties, style);
    return FT_SUCCESS;
}



int ft_set_cell_prop(ft_table_t *table, size_t row, size_t col, uint32_t property, int value)
{
    assert(table);

    if (table->properties == NULL) {
        table->properties = create_table_properties();
        if (table->properties == NULL)
            return FT_MEMORY_ERROR;
    }
    if (table->properties->cell_properties == NULL) {
        table->properties->cell_properties = create_cell_prop_container();
        if (table->properties->cell_properties == NULL) {
            return FT_ERROR;
        }
    }

    if (row == FT_CUR_ROW)
        row = table->cur_row;
    if (row == FT_CUR_COLUMN)
        col = table->cur_col;

    return set_cell_property(table->properties->cell_properties, row, col, property, value);
}

int ft_set_default_cell_prop(uint32_t property, int value)
{
    return set_default_cell_property(property, value);
}


int ft_set_default_tbl_prop(uint32_t property, int value)
{
    return set_default_entire_table_property(property, value);
}

int ft_set_tbl_prop(ft_table_t *table, uint32_t property, int value)
{
    assert(table);

    if (table->properties == NULL) {
        table->properties = create_table_properties();
        if (table->properties == NULL)
            return FT_MEMORY_ERROR;
    }
    return set_entire_table_property(table->properties, property, value);
}

void ft_set_memory_funcs(void *(*f_malloc)(size_t size), void (*f_free)(void *ptr))
{
    set_memory_funcs(f_malloc, f_free);
}

int ft_set_cell_span(ft_table_t *table, size_t row, size_t col, size_t hor_span)
{
    assert(table);
    if (hor_span < 2)
        return FT_EINVAL;

    if (row == FT_CUR_ROW)
        row = table->cur_row;
    if (row == FT_CUR_COLUMN)
        col = table->cur_col;

    fort_row_t *row_p = get_row_and_create_if_not_exists(table, row);
    if (row_p == NULL)
        return FT_ERROR;

    return row_set_cell_span(row_p, col, hor_span);
}

/********************************************************
   End of file "fort_impl.c"
 ********************************************************/


/********************************************************
   Begin of file "fort_utils.c"
 ********************************************************/

/* #include "fort_utils.h" */ /* Commented by amalgamation script */
#ifdef FT_HAVE_WCHAR
#include <wchar.h>
#endif


char g_col_separator = FORT_DEFAULT_COL_SEPARATOR;

/*****************************************************************************
 *               LIBFORT helpers
 *****************************************************************************/

#ifndef FT_MICROSOFT_COMPILER
void *(*fort_malloc)(size_t size) = &malloc;
void (*fort_free)(void *ptr) = &free;
void *(*fort_calloc)(size_t nmemb, size_t size) = &calloc;
void *(*fort_realloc)(void *ptr, size_t size) = &realloc;
#else
static void *local_malloc(size_t size)
{
    return malloc(size);
}

static void local_free(void *ptr)
{
    free(ptr);
}

static void *local_calloc(size_t nmemb, size_t size)
{
    return calloc(nmemb, size);
}

static void *local_realloc(void *ptr, size_t size)
{
    return realloc(ptr, size);
}

void *(*fort_malloc)(size_t size) = &local_malloc;
void (*fort_free)(void *ptr) = &local_free;
void *(*fort_calloc)(size_t nmemb, size_t size) = &local_calloc;
void *(*fort_realloc)(void *ptr, size_t size) = &local_realloc;
#endif

static void *custom_fort_calloc(size_t nmemb, size_t size)
{
    size_t total_size = nmemb * size;
    void *result = F_MALLOC(total_size);
    if (result != NULL)
        memset(result, 0, total_size);
    return result;
}

static void *custom_fort_realloc(void *ptr, size_t size)
{
    if (ptr == NULL)
        return F_MALLOC(size);
    if (size == 0) {
        F_FREE(ptr);
        return NULL;
    }

    void *new_chunk = F_MALLOC(size);
    if (new_chunk == NULL)
        return NULL;

    /*
     * In theory we should copy MIN(size, size allocated for ptr) bytes,
     * but this is rather dummy implementation so we don't care about it
     */
    memcpy(new_chunk, ptr, size);
    F_FREE(ptr);
    return new_chunk;
}


FT_INTERNAL
void set_memory_funcs(void *(*f_malloc)(size_t size), void (*f_free)(void *ptr))
{
    assert((f_malloc == NULL && f_free == NULL) /* Use std functions */
           || (f_malloc != NULL && f_free != NULL) /* Use custom functions */);

    if (f_malloc == NULL && f_free == NULL) {
#ifndef FT_MICROSOFT_COMPILER
        fort_malloc = &malloc;
        fort_free = &free;
        fort_calloc = &calloc;
        fort_realloc = &realloc;
#else
        fort_malloc = &local_malloc;
        fort_free = &local_free;
        fort_calloc = &local_calloc;
        fort_realloc = &local_realloc;
#endif
    } else {
        fort_malloc = f_malloc;
        fort_free = f_free;
        fort_calloc = &custom_fort_calloc;
        fort_realloc = &custom_fort_realloc;
    }

}

FT_INTERNAL
char *fort_strdup(const char *str)
{
    if (str == NULL)
        return NULL;

    size_t sz = strlen(str);
    char *str_copy = (char *)F_MALLOC((sz + 1) * sizeof(char));
    if (str_copy == NULL)
        return NULL;

    strcpy(str_copy, str);
    return str_copy;
}

#if defined(FT_HAVE_WCHAR)
FT_INTERNAL
wchar_t *fort_wcsdup(const wchar_t *str)
{
    if (str == NULL)
        return NULL;

    size_t sz = wcslen(str);
    wchar_t *str_copy = (wchar_t *)F_MALLOC((sz + 1) * sizeof(wchar_t));
    if (str_copy == NULL)
        return NULL;

    wcscpy(str_copy, str);
    return str_copy;
}
#endif


FT_INTERNAL
size_t number_of_columns_in_format_string(const char *fmt)
{
    size_t separator_counter = 0;
    const char *pos = fmt;
    while (1) {
        pos = strchr(pos, g_col_separator);
        if (pos == NULL)
            break;

        separator_counter++;
        ++pos;
    }
    return separator_counter + 1;
}


#if defined(FT_HAVE_WCHAR)
FT_INTERNAL
size_t number_of_columns_in_format_wstring(const wchar_t *fmt)
{
    size_t separator_counter = 0;
    const wchar_t *pos = fmt;
    while (1) {
        pos = wcschr(pos, g_col_separator);
        if (pos == NULL)
            break;

        separator_counter++;
        ++pos;
    }
    return separator_counter + 1;
}
#endif


FT_INTERNAL
int snprint_n_strings(char *buf, size_t length, size_t n, const char *str)
{
    size_t str_len = strlen(str);
    if (length <= n * str_len)
        return -1;

    if (n == 0)
        return 0;

    /* To ensure valid return value it is safely not print such big strings */
    if (n * str_len > INT_MAX)
        return -1;

    if (str_len == 0)
        return 0;

    int status = snprintf(buf, length, "%0*d", (int)(n * str_len), 0);
    if (status < 0)
        return status;

    size_t i = 0;
    for (i = 0; i < n; ++i) {
        const char *str_p = str;
        while (*str_p)
            *(buf++) = *(str_p++);
    }
    return (int)(n * str_len);
}



#if defined(FT_HAVE_WCHAR)
#define WCS_SIZE 64

FT_INTERNAL
int wsnprint_n_string(wchar_t *buf, size_t length, size_t n, const char *str)
{
    size_t str_len = strlen(str);

    /* note: baybe it's, better to return -1 in case of multibyte character
     * strings (not sure this case is done correctly).
     */
    if (str_len > 1) {
        const unsigned char *p = (const unsigned char *)str;
        while (*p) {
            if (*p <= 127)
                p++;
            else {
                wchar_t wcs[WCS_SIZE];
                const char *ptr = str;
                size_t wcs_len;
                mbstate_t mbst;
                memset(&mbst, 0, sizeof(mbst));
                wcs_len = mbsrtowcs(wcs, (const char **)&ptr, WCS_SIZE, &mbst);
                /* for simplicity */
                if ((wcs_len == (size_t) - 1) || wcs_len > 1) {
                    return -1;
                } else {
                    wcs[wcs_len] = L'\0';
                    size_t k = n;
                    while (k) {
                        *buf = *wcs;
                        ++buf;
                        --k;
                    }
                    buf[n] = L'\0';
                    return (int)n;
                }
            }
        }
    }

    if (length <= n * str_len)
        return -1;

    if (n == 0)
        return 0;

    /* To ensure valid return value it is safely not print such big strings */
    if (n * str_len > INT_MAX)
        return -1;

    if (str_len == 0)
        return 0;

    int status = swprintf(buf, length, L"%0*d", (int)(n * str_len), 0);
    if (status < 0)
        return status;

    size_t i = 0;
    for (i = 0; i < n; ++i) {
        const char *str_p = str;
        while (*str_p)
            *(buf++) = (wchar_t) * (str_p++);
    }
    return (int)(n * str_len);
}
#endif

/********************************************************
   End of file "fort_utils.c"
 ********************************************************/


/********************************************************
   Begin of file "properties.c"
 ********************************************************/

/* #include "fort_utils.h" */ /* Commented by amalgamation script */
#include <assert.h>
/* #include "properties.h" */ /* Commented by amalgamation script */
/* #include "vector.h" */ /* Commented by amalgamation script */

#define FT_RESET_COLOR "\033[0m"

static const char *fg_colors[] = {
    "",
    "\033[30m",
    "\033[31m",
    "\033[32m",
    "\033[33m",
    "\033[34m",
    "\033[35m",
    "\033[36m",
    "\033[37m",
    "\033[90m",
    "\033[91m",
    "\033[92m",
    "\033[93m",
    "\033[94m",
    "\033[95m",
    "\033[96m",
    "\033[97m",
};

static const char *bg_colors[] = {
    "",
    "\033[40m",
    "\033[41m",
    "\033[42m",
    "\033[43m",
    "\033[44m",
    "\033[45m",
    "\033[46m",
    "\033[47m",
    "\033[100m",
    "\033[101m",
    "\033[102m",
    "\033[103m",
    "\033[104m",
    "\033[105m",
    "\033[106m",
    "\033[107m",
};

static const char *text_styles[] = {
    "",
    "\033[1m",
    "\033[2m",
    "\033[3m",
    "\033[4m",
    "\033[5m",
    "\033[7m",
    "\033[8m",
};

#define UNIVERSAL_RESET_TAG "\033[0m"

static const size_t n_fg_colors = sizeof(fg_colors) / sizeof(fg_colors[0]);
static const size_t n_bg_colors = sizeof(bg_colors) / sizeof(bg_colors[0]);
static const size_t n_styles = sizeof(text_styles) / sizeof(text_styles[0]);

void get_style_tag_for_cell(const fort_table_properties_t *props,
                            size_t row, size_t col, char *style_tag, size_t sz)
{
    (void)sz;
    size_t i = 0;

    unsigned bg_color_number = get_cell_property_value_hierarcial(props, row, col, FT_CPROP_CELL_BG_COLOR);
    unsigned text_style = get_cell_property_value_hierarcial(props, row, col, FT_CPROP_CELL_TEXT_STYLE);

    style_tag[0] = '\0';

    if (text_style < (1U << n_styles)) {
        for (i = 0; i < n_styles; ++i) {
            if (text_style & (1 << i)) {
                strcat(style_tag, text_styles[i]);
            }
        }
    } else {
        goto error;
    }

    if (bg_color_number < n_bg_colors) {
        strcat(style_tag, bg_colors[bg_color_number]);
    } else {
        goto error;
    }

    return;

error:
    /* shouldn't be here */
    assert(0);
    style_tag[0] = '\0';
    return;
}

void get_reset_style_tag_for_cell(const fort_table_properties_t *props,
                                  size_t row, size_t col, char *reset_style_tag, size_t sz)
{
    (void)sz;
    size_t i = 0;

    unsigned bg_color_number = get_cell_property_value_hierarcial(props, row, col, FT_CPROP_CELL_BG_COLOR);
    unsigned text_style = get_cell_property_value_hierarcial(props, row, col, FT_CPROP_CELL_TEXT_STYLE);

    reset_style_tag[0] = '\0';

    if (text_style < (1U << n_styles)) {
        for (i = 0; i < n_styles; ++i) {
            if (text_style & (1 << i)) {
                if (i != 0) // FT_TSTYLE_DEFAULT
                    goto reset_style;
            }
        }
    } else {
        goto error;
    }

    if (bg_color_number < n_bg_colors) {
        if (bg_color_number)
            goto reset_style;
    } else {
        goto error;
    }

    return;


reset_style:
    strcat(reset_style_tag, UNIVERSAL_RESET_TAG);
    return;

error:
    /* shouldn't be here */
    assert(0);
    reset_style_tag[0] = '\0';
    return;
}


void get_style_tag_for_content(const fort_table_properties_t *props,
                               size_t row, size_t col, char *style_tag, size_t sz)
{
    (void)sz;
    size_t i = 0;

    unsigned text_style = get_cell_property_value_hierarcial(props, row, col, FT_CPROP_CONT_TEXT_STYLE);
    unsigned fg_color_number = get_cell_property_value_hierarcial(props, row, col, FT_CPROP_CONT_FG_COLOR);
    unsigned bg_color_number = get_cell_property_value_hierarcial(props, row, col, FT_CPROP_CONT_BG_COLOR);

    style_tag[0] = '\0';

    if (text_style < (1U << n_styles)) {
        for (i = 0; i < n_styles; ++i) {
            if (text_style & (1 << i)) {
                strcat(style_tag, text_styles[i]);
            }
        }
    } else {
        goto error;
    }

    if (fg_color_number < n_fg_colors) {
        if (fg_color_number)
            strcat(style_tag, fg_colors[fg_color_number]);
    } else {
        goto error;
    }

    if (bg_color_number < n_bg_colors) {
        strcat(style_tag, bg_colors[bg_color_number]);
    } else {
        goto error;
    }

    return;

error:
    /* shouldn't be here */
    assert(0);
    style_tag[0] = '\0';
    return;
}

void get_reset_style_tag_for_content(const fort_table_properties_t *props,
                                     size_t row, size_t col, char *reset_style_tag, size_t sz)
{
    (void)sz;
    size_t i = 0;
    size_t len = 0;

    unsigned text_style = get_cell_property_value_hierarcial(props, row, col, FT_CPROP_CONT_TEXT_STYLE);
    unsigned fg_color_number = get_cell_property_value_hierarcial(props, row, col, FT_CPROP_CONT_FG_COLOR);
    unsigned bg_color_number = get_cell_property_value_hierarcial(props, row, col, FT_CPROP_CONT_BG_COLOR);

    reset_style_tag[0] = '\0';

    if (text_style < (1U << n_styles)) {
        for (i = 0; i < n_styles; ++i) {
            if (text_style & (1 << i)) {
                if (i != 0) // FT_TSTYLE_DEFAULT
                    goto reset_style;
            }
        }
    } else {
        goto error;
    }

    if (fg_color_number < n_fg_colors) {
        if (fg_color_number)
            goto reset_style;
    } else {
        goto error;
    }

    if (bg_color_number < n_bg_colors) {
        if (bg_color_number)
            goto reset_style;
    } else {
        goto error;
    }

    return;


reset_style:
    strcat(reset_style_tag, UNIVERSAL_RESET_TAG);
    len = strlen(reset_style_tag);
    get_style_tag_for_cell(props, row, col, reset_style_tag + len, sz - len);
    return;

error:
    /* shouldn't be here */
    assert(0);
    reset_style_tag[0] = '\0';
    return;
}


static struct fort_cell_props g_default_cell_properties = {
    FT_ANY_ROW,    /* cell_row */
    FT_ANY_COLUMN, /* cell_col */

    /* properties_flags */
    FT_CPROP_MIN_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_ALIGNED_LEFT,  /* align */
    0,      /* cell_padding_top         */
    0,      /* cell_padding_bottom      */
    1,      /* cell_padding_left        */
    1,      /* cell_padding_right       */
    1,      /* cell_empty_string_height */

    FT_ROW_COMMON, /* row_type */
    FT_COLOR_DEFAULT, /* content_fg_color_number */
    FT_COLOR_DEFAULT, /* content_bg_color_number */
    FT_COLOR_DEFAULT, /* cell_bg_color_number */
    FT_TSTYLE_DEFAULT, /* cell_text_style */
    FT_TSTYLE_DEFAULT, /* content_text_style */
};

static int get_prop_value_if_exists_otherwise_default(const struct fort_cell_props *cell_opts, uint32_t property)
{
    if (cell_opts == NULL || !PROP_IS_SET(cell_opts->properties_flags, property)) {
        cell_opts = &g_default_cell_properties;
    }

    switch (property) {
        case FT_CPROP_MIN_WIDTH:
            return cell_opts->col_min_width;
        case FT_CPROP_TEXT_ALIGN:
            return cell_opts->align;
        case FT_CPROP_TOP_PADDING:
            return cell_opts->cell_padding_top;
        case FT_CPROP_BOTTOM_PADDING:
            return cell_opts->cell_padding_bottom;
        case FT_CPROP_LEFT_PADDING:
            return cell_opts->cell_padding_left;
        case FT_CPROP_RIGHT_PADDING:
            return cell_opts->cell_padding_right;
        case FT_CPROP_EMPTY_STR_HEIGHT:
            return cell_opts->cell_empty_string_height;
        case FT_CPROP_ROW_TYPE:
            return cell_opts->row_type;
        case FT_CPROP_CONT_FG_COLOR:
            return cell_opts->content_fg_color_number;
        case FT_CPROP_CONT_BG_COLOR:
            return cell_opts->content_bg_color_number;
        case FT_CPROP_CELL_BG_COLOR:
            return cell_opts->cell_bg_color_number;
        case FT_CPROP_CELL_TEXT_STYLE:
            return cell_opts->cell_text_style;
        case FT_CPROP_CONT_TEXT_STYLE:
            return cell_opts->content_text_style;
        default:
            /* todo: implement later */
            exit(333);
    }
}


FT_INTERNAL
fort_cell_prop_container_t *create_cell_prop_container(void)
{
    fort_cell_prop_container_t *ret = create_vector(sizeof(fort_cell_props_t), DEFAULT_VECTOR_CAPACITY);
    return ret;
}


FT_INTERNAL
void destroy_cell_prop_container(fort_cell_prop_container_t *cont)
{
    if (cont)
        destroy_vector(cont);
}


FT_INTERNAL
const fort_cell_props_t *cget_cell_prop(const fort_cell_prop_container_t *cont, size_t row, size_t col)
{
    assert(cont);
    size_t sz = vector_size(cont);
    size_t i = 0;
    for (i = 0; i < sz; ++i) {
        const fort_cell_props_t *opt = (const fort_cell_props_t *)vector_at_c(cont, i);
        if (opt->cell_row == row && opt->cell_col == col)
            return opt;
    }
    return NULL;
}


FT_INTERNAL
fort_cell_props_t *get_cell_prop_and_create_if_not_exists(fort_cell_prop_container_t *cont, size_t row, size_t col)
{
    assert(cont);
    size_t sz = vector_size(cont);
    size_t i = 0;
    for (i = 0; i < sz; ++i) {
        fort_cell_props_t *opt = (fort_cell_props_t *)vector_at(cont, i);
        if (opt->cell_row == row && opt->cell_col == col)
            return opt;
    }

    fort_cell_props_t opt;
    if (row == FT_ANY_ROW && col == FT_ANY_COLUMN)
        memcpy(&opt, &g_default_cell_properties, sizeof(fort_cell_props_t));
    else
        memset(&opt, 0, sizeof(fort_cell_props_t));

    opt.cell_row = row;
    opt.cell_col = col;
    if (FT_IS_SUCCESS(vector_push(cont, &opt))) {
        return (fort_cell_props_t *)vector_at(cont, sz);
    }

    return NULL;
}


FT_INTERNAL
int get_cell_property_value_hierarcial(const fort_table_properties_t *propertiess, size_t row, size_t column, uint32_t property)
{
    assert(propertiess);
    size_t row_origin = row;

    const fort_cell_props_t *opt = NULL;
    if (propertiess->cell_properties != NULL) {
        while (1) {
            opt = cget_cell_prop(propertiess->cell_properties, row, column);
            if (opt != NULL && PROP_IS_SET(opt->properties_flags, property))
                break;

            if (row != FT_ANY_ROW && column != FT_ANY_COLUMN) {
                row = FT_ANY_ROW;
                continue;
            } else if (row == FT_ANY_ROW && column != FT_ANY_COLUMN) {
                row = row_origin;
                column = FT_ANY_COLUMN;
                continue;
            } else if (row != FT_ANY_ROW  && column == FT_ANY_COLUMN) {
                row = FT_ANY_ROW;
                column = FT_ANY_COLUMN;
                continue;
            }

            opt = NULL;
            break;
        }
    }

    return get_prop_value_if_exists_otherwise_default(opt, property);
}


static fort_status_t set_cell_property_impl(fort_cell_props_t *opt, uint32_t property, int value)
{
    assert(opt);

    PROP_SET(opt->properties_flags, property);
    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_TEXT_ALIGN)) {
        opt->align = (enum ft_text_alignment)value;
    } else if (PROP_IS_SET(property, FT_CPROP_TOP_PADDING)) {
        CHECK_NOT_NEGATIVE(value);
        opt->cell_padding_top = value;
    } else if (PROP_IS_SET(property, FT_CPROP_BOTTOM_PADDING)) {
        CHECK_NOT_NEGATIVE(value);
        opt->cell_padding_bottom = value;
    } else if (PROP_IS_SET(property, FT_CPROP_LEFT_PADDING)) {
        CHECK_NOT_NEGATIVE(value);
        opt->cell_padding_left = value;
    } else if (PROP_IS_SET(property, FT_CPROP_RIGHT_PADDING)) {
        CHECK_NOT_NEGATIVE(value);
        opt->cell_padding_right = value;
    } else if (PROP_IS_SET(property, FT_CPROP_EMPTY_STR_HEIGHT)) {
        CHECK_NOT_NEGATIVE(value);
        opt->cell_empty_string_height = value;
    } else if (PROP_IS_SET(property, FT_CPROP_ROW_TYPE)) {
        opt->row_type = (enum ft_row_type)value;
    } else if (PROP_IS_SET(property, FT_CPROP_CONT_FG_COLOR)) {
        opt->content_fg_color_number = value;
    } else if (PROP_IS_SET(property, FT_CPROP_CONT_BG_COLOR)) {
        opt->content_bg_color_number = value;
    } else if (PROP_IS_SET(property, FT_CPROP_CELL_BG_COLOR)) {
        opt->cell_bg_color_number = value;
    } else if (PROP_IS_SET(property, FT_CPROP_CELL_TEXT_STYLE)) {
        enum ft_text_style v = (enum ft_text_style)value;
        if (v == FT_TSTYLE_DEFAULT) {
            opt->cell_text_style = FT_TSTYLE_DEFAULT;
        } else {
            opt->cell_text_style = (enum ft_text_style)(opt->cell_text_style | v);
        }
    } else if (PROP_IS_SET(property, FT_CPROP_CONT_TEXT_STYLE)) {
        enum ft_text_style v = (enum ft_text_style)value;
        if (v == FT_TSTYLE_DEFAULT) {
            opt->content_text_style = v;
        } else {
            opt->content_text_style = (enum ft_text_style)(opt->content_text_style | v);
        }
    }

    return FT_SUCCESS;

fort_fail:
    return FT_EINVAL;
}


FT_INTERNAL
fort_status_t set_cell_property(fort_cell_prop_container_t *cont, size_t row, size_t col, uint32_t property, int value)
{
    fort_cell_props_t *opt = get_cell_prop_and_create_if_not_exists(cont, row, col);
    if (opt == NULL)
        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;
    */
}


FT_INTERNAL
fort_status_t set_default_cell_property(uint32_t property, int value)
{
    return set_cell_property_impl(&g_default_cell_properties, property, value);
}


#define BASIC_STYLE  {            \
    /* border_chars */            \
    {                             \
     "+", "-", "+", "+",          \
     "|", "|", "|",               \
     "\0", "\0", "\0", "\0",      \
     "+", "-", "+", "+",          \
     "+", "+", "+", "+",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    "+", "-", "+", "+",           \
    "|", "|", "|",                \
    "+", "-", "+", "+",           \
    "+", "-", "+", "+",           \
    "+", "+", "+", "+",           \
    },                            \
    /* separator_chars */         \
    {                             \
    "+", "-", "+", "+",           \
    "+", "+",                     \
    },                            \
}

#define BASIC2_STYLE  {           \
    /* border_chars */            \
    {                             \
     "+", "-", "+", "+",          \
     "|", "|", "|",               \
     "+", "-", "+", "+",          \
     "+", "-", "+", "+",          \
     "+", "+", "+", "+",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    "+", "-", "+", "+",           \
    "|", "|", "|",                \
    "+", "-", "+", "+",           \
    "+", "-", "+", "+",           \
    "+", "+", "+", "+",           \
    },                            \
    /* separator_chars */         \
    {                             \
    "+", "-", "+", "+",           \
    "+", "+",                     \
    },                            \
}

#define SIMPLE_STYLE  {           \
    /* border_chars */            \
    {                             \
     " ", " ", " ", " ",          \
     " ", " ", " ",               \
     "\0", "\0", "\0", "\0",      \
     " ", " ", " ", " ",          \
     " ", " ", " ", " ",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    " ", " ", " ", " ",           \
    " ", " ", " ",                \
    " ", "-", " ", " ",           \
    " ", " ", " ", " ",           \
    " ", "-", " ", "-",           \
    },                            \
    /* separator_chars */         \
    {                             \
    " ", "-", " ", " ",           \
    " ", " ",                     \
    },                            \
}

#define PLAIN_STYLE  {            \
    /* border_chars */            \
    {                             \
     " ", " ", " ", " ",          \
     " ", " ", " ",               \
     "\0", "\0", "\0", "\0",      \
     " ", " ", " ", " ",          \
     " ", " ", " ", " ",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    " ", "-", "-", " ",           \
    " ", " ", " ",                \
    " ", "-", "-", " ",           \
    " ", "-", "-", " ",           \
    " ", "-", " ", "-",           \
    },                            \
    /* separator_chars */         \
    {                             \
    " ", "-", "-", " ",           \
    "-", "-",                     \
    },                            \
}

#define DOT_STYLE  {              \
    /* border_chars */            \
    {                             \
     ".", ".", ".", ".",          \
     ":", ":", ":",               \
     "\0", "\0", "\0", "\0",      \
     ":", ".", ":", ":",          \
     "+", ":", "+", ":",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    ".", ".", ".", ".",           \
    ":", ":", ":",                \
    ":", ".", ":", ":",           \
    ":", ".", ":", ":",           \
    "+", ".", "+", ".",           \
    },                            \
    /* separator_chars */         \
    {                             \
    ":", ".", ":", ":",           \
    ":", ":",                     \
    },                            \
}

#define EMPTY_STYLE  {            \
    /* border_chars */            \
    {                             \
     "\0", "\0", "\0", "\0",      \
     "\0", "\0", "\0",            \
     "\0", "\0", "\0", "\0",      \
     "\0", "\0", "\0", "\0",      \
     "\0", "\0", "\0", "\0",      \
    },                            \
    /* header_border_chars */     \
    {                             \
    "\0", "\0", "\0", "\0",       \
    "\0", "\0", "\0",             \
    "\0", "\0", "\0", "\0",       \
    "\0", "\0", "\0", "\0",       \
    "\0", "\0", "\0", "\0",       \
    },                            \
    /* separator_chars */         \
    {                             \
    "\0", " ", "\0 ", "\0",       \
    "\0", "\0",                   \
    },                            \
}


#define EMPTY2_STYLE  {            \
    /* border_chars */            \
    {                             \
     " ", " ", " ", " ",          \
     " ", " ", " ",               \
     "\0", "\0", "\0", "\0",      \
     " ", " ", " ", " ",          \
     " ", " ", " ", " ",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    " ", " ", " ", " ",           \
    " ", " ", " ",                \
    "\0", "\0", "\0", "\0",       \
    " ", " ", " ", " ",           \
    " ", " ", " ", " ",           \
    },                            \
    /* separator_chars */         \
    {                             \
    " ", " ", " ", " ",           \
    " ", " ",                     \
    },                            \
}

#define SOLID_STYLE  {            \
    /* border_chars */            \
    {                             \
     "┌", "─", "┬", "┐",          \
     "│", "│", "│",               \
     "", "", "", "",              \
     "└", "─", "┴", "╯",          \
     "│", "─", "│", "─",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    "┌", "─", "┬", "┐",           \
    "│", "│", "│",                \
    "├", "─", "┼", "┤",           \
    "└", "─", "┴", "┘",           \
    "┼", "┬", "┼", "┴",           \
    },                            \
    /* separator_chars */         \
    {                             \
    "├", "─", "┼", "┤",           \
    "┬", "┴",                     \
    },                            \
}

#define SOLID_ROUND_STYLE  {      \
    /* border_chars */            \
    {                             \
     "╭", "─", "┬", "╮",          \
     "│", "│", "│",               \
     "", "", "", "",              \
     "╰", "─", "┴", "╯",          \
     "│", "─", "│", "─",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    "╭", "─", "┬", "╮",           \
    "│", "│", "│",                \
    "├", "─", "┼", "┤",           \
    "╰", "─", "┴", "╯",           \
    "┼", "┬", "┼", "┴",           \
    },                            \
    /* separator_chars */         \
    {                             \
    "├", "─", "┼", "┤",           \
    "┬", "┴",                     \
    },                            \
}

#define NICE_STYLE  {             \
    /* border_chars */            \
    {                             \
     "╔", "═", "╦", "╗",          \
     "║", "║", "║",               \
     "", "", "", "",              \
     "╚", "═", "╩", "╝",          \
     "┣", "┻", "┣", "┳",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    "╔", "═", "╦", "╗",           \
    "║", "║", "║",                \
    "╠", "═", "╬", "╣",           \
    "╚", "═", "╩", "╝",           \
    "┣", "╦", "┣", "╩",           \
    },                            \
    /* separator_chars */         \
    {                             \
    "╟", "─", "╫", "╢",           \
    "╥", "╨",                     \
    },                            \
}

#define DOUBLE_STYLE  {         \
    /* border_chars */          \
    {                           \
     "╔", "═", "╦", "╗",        \
     "║", "║", "║",             \
     "", "", "", "",            \
     "╚", "═", "╩", "╝",        \
     "┣", "┻", "┣", "┳",        \
    },                          \
    /* header_border_chars */   \
    {                           \
    "╔", "═", "╦", "╗",         \
    "║", "║", "║",              \
    "╠", "═", "╬", "╣",         \
    "╚", "═", "╩", "╝",         \
    "┣", "╦", "┣", "╩",         \
    },                          \
    /* separator_chars */       \
    {                           \
    "╠", "═", "╬", "╣",         \
    "╦", "╩",                   \
    },                          \
}




#define DOUBLE2_STYLE  {          \
    /* border_chars */            \
    {                             \
     "╔", "═", "╤", "╗",          \
     "║", "│", "║",               \
     "╟", "─", "┼", "╢",          \
     "╚", "═", "╧", "╝",          \
     "├", "┬", "┤", "┴",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    "╔", "═", "╤", "╗",           \
    "║", "│", "║",                \
    "╠", "═", "╪", "╣",           \
    "╚", "═", "╧", "╝",           \
    "├", "╤", "┤", "╧",           \
    },                            \
    /* separator_chars */         \
    {                             \
    "╠", "═", "╪", "╣",           \
    "╤", "╧",                     \
    },                            \
}


#define BOLD_STYLE  {             \
    /* border_chars */            \
    {                             \
     "┏", "━", "┳", "┓",          \
     "┃", "┃", "┃",               \
     "", "", "", "",              \
     "┗", "━", "┻", "┛",          \
     "┣", "┻", "┣", "┳",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    "┏", "━", "┳", "┓",           \
    "┃", "┃", "┃",                \
    "┣", "━", "╋", "┫",           \
    "┗", "━", "┻", "┛",           \
    "┣", "┳", "┣", "┻",           \
    },                            \
    /* separator_chars */         \
    {                             \
    "┣", "━", "╋", "┫",           \
    "┳", "┻",                     \
    },                            \
}

#define BOLD2_STYLE  {            \
    /* border_chars */            \
    {                             \
     "┏", "━", "┯", "┓",          \
     "┃", "│", "┃",               \
     "┠", "─", "┼", "┨",          \
     "┗", "━", "┷", "┛",          \
     "┣", "┬", "┣", "┴",          \
    },                            \
    /* header_border_chars */     \
    {                             \
    "┏", "━", "┯", "┓",           \
    "┃", "│", "┃",                \
    "┣", "━", "┿", "┫",           \
    "┗", "━", "┷", "┛",           \
    "┣", "┯", "┣", "┷",           \
    },                            \
    /* separator_chars */         \
    {                             \
    "┣", "━", "┿", "┫",           \
    "┯", "┷",                     \
    },                            \
}

#define FRAME_STYLE  {             \
    /* border_chars */            \
    {                             \
     "▛", "▀", "▀", "▜",          \
     "▌", "┃", "▐",               \
     "", "", "", "",              \
     "▙", "▄", "▄", "▟",          \
     "┣", "━", "┣", "━"           \
    },                            \
    /* header_border_chars */     \
    {                             \
    "▛", "▀", "▀", "▜",           \
    "▌", "┃", "▐",                \
    "▌", "━", "╋", "▐",           \
    "▙", "▄", "▄", "▟",           \
    "┣", "━", "┣", "━",           \
    },                            \
    /* separator_chars */         \
    {                             \
    "▌", "━", "╋", "▐",           \
    "╋", "╋",                     \
    },                            \
}


struct fort_border_style FORT_BASIC_STYLE = BASIC_STYLE;
struct fort_border_style FORT_BASIC2_STYLE = BASIC2_STYLE;
struct fort_border_style FORT_SIMPLE_STYLE = SIMPLE_STYLE;
struct fort_border_style FORT_PLAIN_STYLE = PLAIN_STYLE;
struct fort_border_style FORT_DOT_STYLE = DOT_STYLE;
struct fort_border_style FORT_EMPTY_STYLE = EMPTY_STYLE;
struct fort_border_style FORT_EMPTY2_STYLE = EMPTY2_STYLE;
struct fort_border_style FORT_SOLID_STYLE = SOLID_STYLE;
struct fort_border_style FORT_SOLID_ROUND_STYLE = SOLID_ROUND_STYLE;
struct fort_border_style FORT_NICE_STYLE = NICE_STYLE;
struct fort_border_style FORT_DOUBLE_STYLE = DOUBLE_STYLE;
struct fort_border_style FORT_DOUBLE2_STYLE = DOUBLE2_STYLE;
struct fort_border_style FORT_BOLD_STYLE = BOLD_STYLE;
struct fort_border_style FORT_BOLD2_STYLE = BOLD2_STYLE;
struct fort_border_style FORT_FRAME_STYLE = FRAME_STYLE;



fort_entire_table_properties_t g_entire_table_properties = {
    0, /* left_margin */
    0, /* top_margin */
    0, /* right_margin */
    0, /* bottom_margin */
};

static fort_status_t set_entire_table_property_internal(fort_entire_table_properties_t *properties, uint32_t property, int value)
{
    assert(properties);
    CHECK_NOT_NEGATIVE(value);
    if (PROP_IS_SET(property, FT_TPROP_LEFT_MARGIN)) {
        properties->left_margin = value;
    } else if (PROP_IS_SET(property, FT_TPROP_TOP_MARGIN)) {
        properties->top_margin = value;
    } else if (PROP_IS_SET(property, FT_TPROP_RIGHT_MARGIN)) {
        properties->right_margin = value;
    } else if (PROP_IS_SET(property, FT_TPROP_BOTTOM_MARGIN)) {
        properties->bottom_margin = value;
    } else {
        return FT_EINVAL;
    }
    return FT_SUCCESS;

fort_fail:
    return FT_EINVAL;
}


FT_INTERNAL
fort_status_t set_entire_table_property(fort_table_properties_t *table_properties, uint32_t property, int value)
{
    assert(table_properties);
    return set_entire_table_property_internal(&table_properties->entire_table_properties, property, value);
}


FT_INTERNAL
fort_status_t set_default_entire_table_property(uint32_t property, int value)
{
    return set_entire_table_property_internal(&g_entire_table_properties, property, value);
}


FT_INTERNAL
size_t max_border_elem_strlen(struct fort_table_properties *properties)
{
    assert(properties);
    size_t result = 1;
    int i = 0;
    for (i = 0; i < BorderItemPosSize; ++i) {
        result = MAX(result, strlen(properties->border_style.border_chars[i]));
    }

    for (i = 0; i < BorderItemPosSize; ++i) {
        result = MAX(result, strlen(properties->border_style.header_border_chars[i]));
    }

    for (i = 0; i < SepratorItemPosSize; ++i) {
        result = MAX(result, strlen(properties->border_style.separator_chars[i]));
    }
    return result;
}


fort_table_properties_t g_table_properties = {
    /* border_style */
    BASIC_STYLE,
    NULL,     /* cell_properties */
    /* entire_table_properties */
    {
        0, /* left_margin */
        0, /* top_margin */
        0, /* right_margin */
        0  /* bottom_margin */
    }
};


FT_INTERNAL
fort_table_properties_t *create_table_properties(void)
{
    fort_table_properties_t *properties = (fort_table_properties_t *)F_CALLOC(sizeof(fort_table_properties_t), 1);
    if (properties == NULL) {
        return NULL;
    }
    memcpy(properties, &g_table_properties, sizeof(fort_table_properties_t));
    properties->cell_properties = create_cell_prop_container();
    if (properties->cell_properties == NULL) {
        destroy_table_properties(properties);
        return NULL;
    }
    memcpy(&properties->entire_table_properties, &g_entire_table_properties, sizeof(fort_entire_table_properties_t));
    return properties;
}

FT_INTERNAL
void destroy_table_properties(fort_table_properties_t *properties)
{
    if (properties == NULL)
        return;

    if (properties->cell_properties != NULL) {
        destroy_cell_prop_container(properties->cell_properties);
    }
    F_FREE(properties);
}

static
fort_cell_prop_container_t *copy_cell_properties(fort_cell_prop_container_t *cont)
{
    fort_cell_prop_container_t *result = create_cell_prop_container();
    if (result == NULL)
        return NULL;

    size_t i = 0;
    size_t sz = vector_size(cont);
    for (i = 0; i < sz; ++i) {
        fort_cell_props_t *opt = (fort_cell_props_t *)vector_at(cont, i);
        if (FT_IS_ERROR(vector_push(result, opt))) {
            destroy_cell_prop_container(result);
            return NULL;
        }
    }
    return result;
}

FT_INTERNAL
fort_table_properties_t *copy_table_properties(const fort_table_properties_t *properties)
{
    fort_table_properties_t *new_opt = create_table_properties();
    if (new_opt == NULL)
        return NULL;

    destroy_vector(new_opt->cell_properties);
    new_opt->cell_properties = copy_cell_properties(properties->cell_properties);
    if (new_opt->cell_properties == NULL) {
        destroy_table_properties(new_opt);
        return NULL;
    }

    memcpy(&new_opt->border_style, &properties->border_style, sizeof(struct fort_border_style));
    memcpy(&new_opt->entire_table_properties,
           &properties->entire_table_properties, sizeof(fort_entire_table_properties_t));

    return new_opt;
}

/********************************************************
   End of file "properties.c"
 ********************************************************/


/********************************************************
   Begin of file "row.c"
 ********************************************************/

#include <assert.h>
#include <ctype.h>
/* #include "row.h" */ /* Commented by amalgamation script */
/* #include "cell.h" */ /* Commented by amalgamation script */
/* #include "string_buffer.h" */ /* Commented by amalgamation script */
/* #include "vector.h" */ /* Commented by amalgamation script */


struct fort_row {
    vector_t *cells;
    /*enum ft_row_type type;*/
};


FT_INTERNAL
fort_row_t *create_row(void)
{
    fort_row_t *row = (fort_row_t *)F_CALLOC(sizeof(fort_row_t), 1);
    if (row == NULL)
        return NULL;
    row->cells = create_vector(sizeof(fort_cell_t *), DEFAULT_VECTOR_CAPACITY);
    if (row->cells == NULL) {
        F_FREE(row);
        return NULL;
    }

    /*
    row->is_header = F_FALSE;
    row->type = FT_ROW_COMMON;
    */
    return row;
}

FT_INTERNAL
void destroy_row(fort_row_t *row)
{
    if (row == NULL)
        return;

    if (row->cells) {
        size_t i = 0;
        size_t cells_n = vector_size(row->cells);
        for (i = 0; i < cells_n; ++i) {
            fort_cell_t *cell = *(fort_cell_t **)vector_at(row->cells, i);
            destroy_cell(cell);
        }
        destroy_vector(row->cells);
    }

    F_FREE(row);
}

FT_INTERNAL
fort_row_t *copy_row(fort_row_t *row)
{
    assert(row);
    fort_row_t *result = create_row();
    if (result == NULL)
        return NULL;

    size_t i = 0;
    size_t cols_n = vector_size(row->cells);
    for (i = 0; i < cols_n; ++i) {
        fort_cell_t *cell = *(fort_cell_t **)vector_at(row->cells, i);
        fort_cell_t *new_cell = copy_cell(cell);
        if (new_cell == NULL) {
            destroy_row(result);
            return NULL;
        }
        vector_push(result->cells, &new_cell);
    }

    return result;
}

FT_INTERNAL
size_t columns_in_row(const fort_row_t *row)
{
    if (row == NULL || row->cells == NULL)
        return 0;

    return vector_size(row->cells);
}


static
fort_cell_t *get_cell_implementation(fort_row_t *row, size_t col, enum PolicyOnNull policy)
{
    if (row == NULL || row->cells == NULL) {
        return NULL;
    }

    switch (policy) {
        case DoNotCreate:
            if (col < columns_in_row(row)) {
                return *(fort_cell_t **)vector_at(row->cells, col);
            }
            return NULL;
        case Create:
            while (col >= columns_in_row(row)) {
                fort_cell_t *new_cell = create_cell();
                if (new_cell == NULL)
                    return NULL;
                if (FT_IS_ERROR(vector_push(row->cells, &new_cell))) {
                    destroy_cell(new_cell);
                    return NULL;
                }
            }
            return *(fort_cell_t **)vector_at(row->cells, col);
    }

    assert(0 && "Shouldn't be here!");
    return NULL;
}


FT_INTERNAL
fort_cell_t *get_cell(fort_row_t *row, size_t col)
{
    return get_cell_implementation(row, col, DoNotCreate);
}


FT_INTERNAL
const fort_cell_t *get_cell_c(const fort_row_t *row, size_t col)
{
    return get_cell((fort_row_t *)row, col);
}


FT_INTERNAL
fort_cell_t *get_cell_and_create_if_not_exists(fort_row_t *row, size_t col)
{
    return get_cell_implementation(row, col, Create);
}


FT_INTERNAL
fort_status_t swap_row(fort_row_t *cur_row, fort_row_t *ins_row, size_t pos)
{
    assert(cur_row);
    assert(ins_row);
    size_t cur_sz = vector_size(cur_row->cells);
    if (cur_sz == 0 && pos == 0) {
        fort_row_t tmp;
        memcpy(&tmp, cur_row, sizeof(fort_row_t));
        memcpy(cur_row, ins_row, sizeof(fort_row_t));
        memcpy(ins_row, &tmp, sizeof(fort_row_t));
        return FT_SUCCESS;
    }

    return vector_swap(cur_row->cells, ins_row->cells, pos);
}


FT_INTERNAL
size_t group_cell_number(const fort_row_t *row, size_t master_cell_col)
{
    assert(row);
    const fort_cell_t *master_cell = get_cell_c(row, master_cell_col);
    if (master_cell == NULL)
        return 0;

    if (get_cell_type(master_cell) != GroupMasterCell)
        return 1;

    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;
}


FT_INTERNAL
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;
}


FT_INTERNAL
fort_status_t row_set_cell_span(fort_row_t *row, size_t cell_column, size_t hor_span)
{
    assert(row);

    if (hor_span < 2)
        return FT_EINVAL;

    fort_cell_t *main_cell = get_cell_and_create_if_not_exists(row, cell_column);
    if (main_cell == NULL) {
        return FT_ERROR;
    }
    set_cell_type(main_cell, GroupMasterCell);
    --hor_span;
    ++cell_column;

    while (hor_span) {
        fort_cell_t *slave_cell = get_cell_and_create_if_not_exists(row, cell_column);
        if (slave_cell == NULL) {
            return FT_ERROR;
        }
        set_cell_type(slave_cell, GroupSlaveCell);
        --hor_span;
        ++cell_column;
    }

    return FT_SUCCESS;
}


FT_INTERNAL
int print_row_separator(char *buffer, size_t buffer_sz,
                        const size_t *col_width_arr, size_t cols,
                        const fort_row_t *upper_row, const fort_row_t *lower_row,
                        enum HorSeparatorPos separatorPos,
                        const separator_t *sep, const context_t *context)
{
    int (*snprint_n_strings_)(char *, size_t, size_t, const char *) = snprint_n_strings;

    assert(buffer);
    assert(context);

    const char *space_char = " ";
    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 = (enum CellType *)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;
    }


    size_t written = 0;
    int tmp = 0;

    enum ft_row_type lower_row_type = FT_ROW_COMMON;
    if (lower_row != NULL) {
        lower_row_type = (enum ft_row_type)get_cell_property_value_hierarcial(context->table_properties, context->row, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE);
    }
    enum ft_row_type upper_row_type = FT_ROW_COMMON;
    if (upper_row != NULL) {
        upper_row_type = (enum ft_row_type)get_cell_property_value_hierarcial(context->table_properties, context->row - 1, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE);
    }

    /* Row separator anatomy
     *
     *  |      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;
    border_chars = (border_chars_point_t)&context->table_properties->border_style.border_chars;
    if (upper_row_type == FT_ROW_HEADER || lower_row_type == FT_ROW_HEADER) {
        border_chars = (border_chars_point_t)&context->table_properties->border_style.header_border_chars;
    }

    if (sep && sep->enabled) {
        L = &(context->table_properties->border_style.separator_chars[LH_sip]);
        I = &(context->table_properties->border_style.separator_chars[IH_sip]);
        IV = &(context->table_properties->border_style.separator_chars[II_sip]);
        R = &(context->table_properties->border_style.separator_chars[RH_sip]);

        IT = &(context->table_properties->border_style.separator_chars[TI_sip]);
        IB = &(context->table_properties->border_style.separator_chars[BI_sip]);
        II = &(context->table_properties->border_style.separator_chars[IH_sip]);

        if (lower_row == NULL) {
            L = &(*border_chars)[BL_bip];
            R = &(*border_chars)[BR_bip];
        } else if (upper_row == NULL) {
            L = &(*border_chars)[TL_bip];
            R = &(*border_chars)[TR_bip];
        }
    } else {
        switch (separatorPos) {
            case TopSeparator:
                L = &(*border_chars)[TL_bip];
                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];
                */
                IT = &(*border_chars)[TI_bip];
                IB = &(*border_chars)[BI_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;
        }
    }

    size_t i = 0;

    /* If all chars are not printable, skip line separator */
    /* todo: add processing for wchar_t */
    /*
    if (!isprint(*L) && !isprint(*I) && !isprint(*IV) && !isprint(*R)) {
        status = 0;
        goto clear;
    }
    */
    if ((strlen(*L) == 0 || (strlen(*L) == 1 && !isprint(**L)))
        && (strlen(*I) == 0 || (strlen(*I) == 1 && !isprint(**I)))
        && (strlen(*IV) == 0 || (strlen(*IV) == 1 && !isprint(**IV)))
        && (strlen(*R) == 0 || (strlen(*R) == 1 && !isprint(**R)))) {
        status = 0;
        goto clear;
    }

    /* Print left margin */
    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, context->table_properties->entire_table_properties.left_margin, space_char));

    for (i = 0; i < cols; ++i) {
        if (i == 0) {
            CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, *L));
        } else {
            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_strings_(buffer + written, buffer_sz - written, 1, *IV));
            } else if (top_row_types[i] == GroupSlaveCell && bottom_row_types[i] == GroupSlaveCell) {
                CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, *II));
            } else if (top_row_types[i] == GroupSlaveCell) {
                CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, *IT));
            } else {
                CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, *IB));
            }
        }
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, col_width_arr[i], *I));
    }
    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, *R));

    /* Print right margin */
    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, context->table_properties->entire_table_properties.right_margin, space_char));

    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, "\n"));

    status = (int)written;

clear:
    F_FREE(top_row_types);
    return status;
}


#ifdef FT_HAVE_WCHAR
FT_INTERNAL
int wprint_row_separator(wchar_t *buffer, size_t buffer_sz,
                         const size_t *col_width_arr, size_t cols,
                         const fort_row_t *upper_row, const fort_row_t *lower_row,
                         enum HorSeparatorPos separatorPos, const separator_t *sep,
                         const context_t *context)
{
    int (*snprint_n_strings_)(wchar_t *, size_t, size_t, const char *) = wsnprint_n_string;

    assert(buffer);
    assert(context);

    const char *space_char = " ";
    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 = (enum CellType *)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;
    }


    size_t written = 0;
    int tmp = 0;

    enum ft_row_type lower_row_type = FT_ROW_COMMON;
    if (lower_row != NULL) {
        lower_row_type = (enum ft_row_type)get_cell_property_value_hierarcial(context->table_properties, context->row, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE);
    }
    enum ft_row_type upper_row_type = FT_ROW_COMMON;
    if (upper_row != NULL) {
        upper_row_type = (enum ft_row_type)get_cell_property_value_hierarcial(context->table_properties, context->row - 1, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE);
    }

    /* Row separator anatomy
     *
     *  |      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;
    border_chars = (border_chars_point_t)&context->table_properties->border_style.border_chars;
    if (upper_row_type == FT_ROW_HEADER || lower_row_type == FT_ROW_HEADER) {
        border_chars = (border_chars_point_t)&context->table_properties->border_style.header_border_chars;
    }

    if (sep && sep->enabled) {
        L = &(context->table_properties->border_style.separator_chars[LH_sip]);
        I = &(context->table_properties->border_style.separator_chars[IH_sip]);
        IV = &(context->table_properties->border_style.separator_chars[II_sip]);
        R = &(context->table_properties->border_style.separator_chars[RH_sip]);

        IT = &(context->table_properties->border_style.separator_chars[TI_sip]);
        IB = &(context->table_properties->border_style.separator_chars[BI_sip]);
        II = &(context->table_properties->border_style.separator_chars[IH_sip]);

        if (lower_row == NULL) {
            L = &(*border_chars)[BL_bip];
            R = &(*border_chars)[BR_bip];
        } else if (upper_row == NULL) {
            L = &(*border_chars)[TL_bip];
            R = &(*border_chars)[TR_bip];
        }
    } else {
        switch (separatorPos) {
            case TopSeparator:
                L = &(*border_chars)[TL_bip];
                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];
                */
                IT = &(*border_chars)[TI_bip];
                IB = &(*border_chars)[BI_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;
        }
    }

    size_t i = 0;

    /* If all chars are not printable, skip line separator */
    /* todo: add processing for wchar_t */
    /*
    if (!isprint(*L) && !isprint(*I) && !isprint(*IV) && !isprint(*R)) {
        status = 0;
        goto clear;
    }
    */
    if ((strlen(*L) == 0 || (strlen(*L) == 1 && !isprint(**L)))
        && (strlen(*I) == 0 || (strlen(*I) == 1 && !isprint(**I)))
        && (strlen(*IV) == 0 || (strlen(*IV) == 1 && !isprint(**IV)))
        && (strlen(*R) == 0 || (strlen(*R) == 1 && !isprint(**R)))) {
        status = 0;
        goto clear;
    }

    /* Print left margin */
    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, context->table_properties->entire_table_properties.left_margin, space_char));

    for (i = 0; i < cols; ++i) {
        if (i == 0) {
            CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, *L));
        } else {
            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_strings_(buffer + written, buffer_sz - written, 1, *IV));
            } else if (top_row_types[i] == GroupSlaveCell && bottom_row_types[i] == GroupSlaveCell) {
                CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, *II));
            } else if (top_row_types[i] == GroupSlaveCell) {
                CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, *IT));
            } else {
                CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, *IB));
            }
        }
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, col_width_arr[i], *I));
    }
    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, *R));

    /* Print right margin */
    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, context->table_properties->entire_table_properties.right_margin, space_char));

    CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buffer_sz - written, 1, "\n"));

    status = (int)written;

clear:
    F_FREE(top_row_types);
    return status;
}

#endif

FT_INTERNAL
fort_row_t *create_row_from_string(const char *str)
{
    typedef char char_type;
    char_type *(*strdup_)(const char_type * str) = F_STRDUP;
    const char_type zero_char = '\0';
    fort_status_t (*fill_cell_from_string_)(fort_cell_t *cell, const char *str) = fill_cell_from_string;
    const char_type *const zero_string = "";
#define STRCHR strchr

    char_type *pos = NULL;
    char_type *base_pos = NULL;
    size_t number_of_separators = 0;

    fort_row_t *row = create_row();
    if (row == NULL)
        return NULL;

    if (str == NULL)
        return row;

    char_type *str_copy = strdup_(str);
    if (str_copy == NULL)
        goto clear;

    pos = str_copy;
    base_pos = str_copy;
    number_of_separators = 0;
    while (*pos) {
        pos = STRCHR(pos, g_col_separator);
        if (pos != NULL) {
            *(pos) = zero_char;
            ++pos;
            number_of_separators++;
        }

        fort_cell_t *cell = create_cell();
        if (cell == NULL)
            goto clear;

        int status = fill_cell_from_string_(cell, base_pos);
        if (FT_IS_ERROR(status)) {
            destroy_cell(cell);
            goto clear;
        }

        status = vector_push(row->cells, &cell);
        if (FT_IS_ERROR(status)) {
            destroy_cell(cell);
            goto clear;
        }

        if (pos == NULL)
            break;
        base_pos = pos;
    }

    /* special case if in format string last cell is empty */
    while (vector_size(row->cells) < (number_of_separators + 1)) {
        fort_cell_t *cell = create_cell();
        if (cell == NULL)
            goto clear;

        int status = fill_cell_from_string_(cell, zero_string);
        if (FT_IS_ERROR(status)) {
            destroy_cell(cell);
            goto clear;
        }

        status = vector_push(row->cells, &cell);
        if (FT_IS_ERROR(status)) {
            destroy_cell(cell);
            goto clear;
        }
    }

    F_FREE(str_copy);
    return row;

clear:
    destroy_row(row);
    F_FREE(str_copy);
    return NULL;

#undef STRCHR
}


#ifdef FT_HAVE_WCHAR
FT_INTERNAL
fort_row_t *create_row_from_wstring(const wchar_t *str)
{
    typedef wchar_t char_type;
    char_type *(*strdup_)(const char_type * str) = F_WCSDUP;
    const char_type zero_char = L'\0';
    fort_status_t (*fill_cell_from_string_)(fort_cell_t *cell, const wchar_t *str) = fill_cell_from_wstring;
    const char_type *const zero_string = L"";
#define STRCHR wcschr

    char_type *pos = NULL;
    char_type *base_pos = NULL;
    size_t number_of_separators = 0;

    fort_row_t *row = create_row();
    if (row == NULL)
        return NULL;

    if (str == NULL)
        return row;

    char_type *str_copy = strdup_(str);
    if (str_copy == NULL)
        goto clear;

    pos = str_copy;
    base_pos = str_copy;
    number_of_separators = 0;
    while (*pos) {
        pos = STRCHR(pos, g_col_separator);
        if (pos != NULL) {
            *(pos) = zero_char;
            ++pos;
            number_of_separators++;
        }

        fort_cell_t *cell = create_cell();
        if (cell == NULL)
            goto clear;

        int status = fill_cell_from_string_(cell, base_pos);
        if (FT_IS_ERROR(status)) {
            destroy_cell(cell);
            goto clear;
        }

        status = vector_push(row->cells, &cell);
        if (FT_IS_ERROR(status)) {
            destroy_cell(cell);
            goto clear;
        }

        if (pos == NULL)
            break;
        base_pos = pos;
    }

    /* special case if in format string last cell is empty */
    while (vector_size(row->cells) < (number_of_separators + 1)) {
        fort_cell_t *cell = create_cell();
        if (cell == NULL)
            goto clear;

        int status = fill_cell_from_string_(cell, zero_string);
        if (FT_IS_ERROR(status)) {
            destroy_cell(cell);
            goto clear;
        }

        status = vector_push(row->cells, &cell);
        if (FT_IS_ERROR(status)) {
            destroy_cell(cell);
            goto clear;
        }
    }

    F_FREE(str_copy);
    return row;

clear:
    destroy_row(row);
    F_FREE(str_copy);
    return NULL;
#undef STRCHR
}
#endif

FT_INTERNAL
fort_row_t *create_row_from_fmt_string(const char  *fmt, va_list *va_args)
{
#define VSNPRINTF vsnprintf
#define STR_FILED cstr
#define CREATE_ROW_FROM_STRING create_row_from_string
#define NUMBER_OF_COLUMNS_IN_FORMAT_STRING number_of_columns_in_format_string
#define FILL_CELL_FROM_STRING fill_cell_from_string
#define STR_BUF_TYPE CharBuf

    string_buffer_t *buffer = create_string_buffer(DEFAULT_STR_BUF_SIZE, STR_BUF_TYPE);
    if (buffer == NULL)
        return NULL;

    size_t cols_origin = NUMBER_OF_COLUMNS_IN_FORMAT_STRING(fmt);
    size_t cols = 0;

    while (1) {
        va_list va;
        va_copy(va, *va_args);
        int virtual_sz = VSNPRINTF(buffer->str.STR_FILED, string_buffer_capacity(buffer), fmt, va);
        va_end(va);
        /* If error encountered */
        if (virtual_sz < 0)
            goto clear;

        /* Successful write */
        if ((size_t)virtual_sz < string_buffer_capacity(buffer))
            break;

        /* Otherwise buffer was too small, so incr. buffer size ant try again. */
        if (!FT_IS_SUCCESS(realloc_string_buffer_without_copy(buffer)))
            goto clear;
    }

    cols = NUMBER_OF_COLUMNS_IN_FORMAT_STRING(buffer->str.STR_FILED);
    if (cols == cols_origin) {

        fort_row_t *row = CREATE_ROW_FROM_STRING(buffer->str.STR_FILED);
        if (row == NULL) {
            goto clear;
        }

        destroy_string_buffer(buffer);
        return row;
    }

    if (cols_origin == 1) {
        fort_row_t *row = create_row();
        if (row == NULL) {
            goto clear;
        }

        fort_cell_t *cell = get_cell_and_create_if_not_exists(row, 0);
        if (cell == NULL) {
            destroy_row(row);
            goto clear;
        }

        fort_status_t result = FILL_CELL_FROM_STRING(cell, buffer->str.STR_FILED);
        if (FT_IS_ERROR(result)) {
            destroy_row(row);
            goto clear;
        }

        destroy_string_buffer(buffer);
        return row;
    }

    /*
     * todo: add processing of cols != cols_origin in a general way
     * (when cols_origin != 1).
     */

clear:
    destroy_string_buffer(buffer);
    return NULL;
#undef VSNPRINTF
#undef STR_FILED
#undef CREATE_ROW_FROM_STRING
#undef NUMBER_OF_COLUMNS_IN_FORMAT_STRING
#undef FILL_CELL_FROM_STRING
#undef STR_BUF_TYPE
}

#ifdef FT_HAVE_WCHAR
FT_INTERNAL
fort_row_t *create_row_from_fmt_wstring(const wchar_t  *fmt, va_list *va_args)
{
#define VSNPRINTF vswprintf
#define STR_FILED wstr
#define CREATE_ROW_FROM_STRING create_row_from_wstring
#define NUMBER_OF_COLUMNS_IN_FORMAT_STRING number_of_columns_in_format_wstring
#define FILL_CELL_FROM_STRING fill_cell_from_wstring
#define STR_BUF_TYPE WCharBuf

    string_buffer_t *buffer = create_string_buffer(DEFAULT_STR_BUF_SIZE, STR_BUF_TYPE);
    if (buffer == NULL)
        return NULL;

    size_t cols_origin = NUMBER_OF_COLUMNS_IN_FORMAT_STRING(fmt);
    size_t cols = 0;

    while (1) {
        va_list va;
        va_copy(va, *va_args);
        int virtual_sz = VSNPRINTF(buffer->str.STR_FILED, string_buffer_capacity(buffer), fmt, va);
        va_end(va);
        /* If error encountered */
        if (virtual_sz < 0)
            goto clear;

        /* Successful write */
        if ((size_t)virtual_sz < string_buffer_capacity(buffer))
            break;

        /* Otherwise buffer was too small, so incr. buffer size ant try again. */
        if (!FT_IS_SUCCESS(realloc_string_buffer_without_copy(buffer)))
            goto clear;
    }

    cols = NUMBER_OF_COLUMNS_IN_FORMAT_STRING(buffer->str.STR_FILED);
    if (cols == cols_origin) {

        fort_row_t *row = CREATE_ROW_FROM_STRING(buffer->str.STR_FILED);
        if (row == NULL) {
            goto clear;
        }

        destroy_string_buffer(buffer);
        return row;
    }

    if (cols_origin == 1) {
        fort_row_t *row = create_row();
        if (row == NULL) {
            goto clear;
        }

        fort_cell_t *cell = get_cell_and_create_if_not_exists(row, 0);
        if (cell == NULL) {
            destroy_row(row);
            goto clear;
        }

        fort_status_t result = FILL_CELL_FROM_STRING(cell, buffer->str.STR_FILED);
        if (FT_IS_ERROR(result)) {
            destroy_row(row);
            goto clear;
        }

        destroy_string_buffer(buffer);
        return row;
    }

    /*
     * todo: add processing of cols != cols_origin in a general way
     * (when cols_origin != 1).
     */

clear:
    destroy_string_buffer(buffer);
    return NULL;
#undef VSNPRINTF
#undef STR_FILED
#undef CREATE_ROW_FROM_STRING
#undef NUMBER_OF_COLUMNS_IN_FORMAT_STRING
#undef FILL_CELL_FROM_STRING
#undef STR_BUF_TYPE
}
#endif


FT_INTERNAL
int snprintf_row(const fort_row_t *row, char *buffer, size_t buf_sz, size_t *col_width_arr, size_t col_width_arr_sz,
                 size_t row_height, const context_t *context)
{
    int (*snprint_n_strings_)(char *, size_t, size_t, const char *) = snprint_n_strings;
    int (*cell_printf_)(fort_cell_t *, size_t, char *, size_t, const context_t *) = cell_printf;

    assert(context);
    const char *space_char = " ";
    const char *new_line_char = "\n";

    if (row == NULL)
        return -1;

    size_t cols_in_row = columns_in_row(row);
    if (cols_in_row > col_width_arr_sz)
        return -1;

    /*  Row separator anatomy
     *
     *  L    data    IV    data   IV   data    R
     */

    typedef const char *(*border_chars_point_t)[BorderItemPosSize];
    enum ft_row_type row_type = (enum ft_row_type)get_cell_property_value_hierarcial(context->table_properties, context->row, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE);
    const char *(*bord_chars)[BorderItemPosSize] = (row_type == FT_ROW_HEADER)
            ? (border_chars_point_t)(&context->table_properties->border_style.header_border_chars)
            : (border_chars_point_t)(&context->table_properties->border_style.border_chars);
    const char **L = &(*bord_chars)[LL_bip];
    const char **IV = &(*bord_chars)[IV_bip];
    const char **R = &(*bord_chars)[RR_bip];


    size_t written = 0;
    int tmp = 0;
    size_t i = 0;
    for (i = 0; i < row_height; ++i) {
        /* Print left margin */
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, context->table_properties->entire_table_properties.left_margin, space_char));

        /* Print left table boundary */
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, 1, *L));
        size_t j = 0;
        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);
                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_strings_(buffer + written, buf_sz - written, col_width_arr[j], space_char));
            }

            /* Print boundary between cells */
            if (j < col_width_arr_sz - 1)
                CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, 1, *IV));

            ++j;
        }

        /* Print right table boundary */
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, 1, *R));

        /* Print right margin */
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, context->table_properties->entire_table_properties.right_margin, space_char));

        /* Print new line character */
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, 1, new_line_char));
    }
    return (int)written;

clear:
    return -1;
}


#ifdef FT_HAVE_WCHAR
FT_INTERNAL
int wsnprintf_row(const fort_row_t *row, wchar_t *buffer, size_t buf_sz, size_t *col_width_arr, size_t col_width_arr_sz,
                  size_t row_height, const context_t *context)
{
    int (*snprint_n_strings_)(wchar_t *, size_t, size_t, const char *) = wsnprint_n_string;
    int (*cell_printf_)(fort_cell_t *, size_t, wchar_t *, size_t, const context_t *) = cell_wprintf;

    assert(context);
    const char *space_char = " ";
    const char *new_line_char = "\n";

    if (row == NULL)
        return -1;

    size_t cols_in_row = columns_in_row(row);
    if (cols_in_row > col_width_arr_sz)
        return -1;

    /*  Row separator anatomy
     *
     *  L    data    IV    data   IV   data    R
     */

    typedef const char *(*border_chars_point_t)[BorderItemPosSize];
    enum ft_row_type row_type = (enum ft_row_type)get_cell_property_value_hierarcial(context->table_properties, context->row, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE);
    const char *(*bord_chars)[BorderItemPosSize] = (row_type == FT_ROW_HEADER)
            ? (border_chars_point_t)(&context->table_properties->border_style.header_border_chars)
            : (border_chars_point_t)(&context->table_properties->border_style.border_chars);
    const char **L = &(*bord_chars)[LL_bip];
    const char **IV = &(*bord_chars)[IV_bip];
    const char **R = &(*bord_chars)[RR_bip];


    size_t written = 0;
    int tmp = 0;
    size_t i = 0;
    for (i = 0; i < row_height; ++i) {
        /* Print left margin */
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, context->table_properties->entire_table_properties.left_margin, space_char));

        /* Print left table boundary */
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, 1, *L));
        size_t j = 0;
        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);
                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_strings_(buffer + written, buf_sz - written, col_width_arr[j], space_char));
            }

            /* Print boundary between cells */
            if (j < col_width_arr_sz - 1)
                CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, 1, *IV));

            ++j;
        }

        /* Print right table boundary */
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, 1, *R));

        /* Print right margin */
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, context->table_properties->entire_table_properties.right_margin, space_char));

        /* Print new line character */
        CHCK_RSLT_ADD_TO_WRITTEN(snprint_n_strings_(buffer + written, buf_sz - written, 1, new_line_char));
    }
    return (int)written;

clear:
    return -1;
}

#endif



/********************************************************
   End of file "row.c"
 ********************************************************/


/********************************************************
   Begin of file "string_buffer.c"
 ********************************************************/

/* #include "string_buffer.h" */ /* Commented by amalgamation script */
/* #include "properties.h" */ /* Commented by amalgamation script */
/* #include "wcwidth.h" */ /* Commented by amalgamation script */
#include <assert.h>
#include <stddef.h>
#include <wchar.h>


static ptrdiff_t str_iter_width(const char *beg, const char *end)
{
    assert(end >= beg);
    return (end - beg);
}


#ifdef FT_HAVE_WCHAR
static ptrdiff_t wcs_iter_width(const wchar_t *beg, const wchar_t *end)
{
    assert(end >= beg);
    return mk_wcswidth(beg, (size_t)(end - beg));
}
#endif /* FT_HAVE_WCHAR */


static size_t buf_str_len(const string_buffer_t *buf)
{
    assert(buf);
    if (buf->type == CharBuf) {
        return strlen(buf->str.cstr);
    } else {
        return wcslen(buf->str.wstr);
    }
}


FT_INTERNAL
size_t strchr_count(const char *str, char ch)
{
    if (str == NULL)
        return 0;

    size_t count = 0;
    str = strchr(str, ch);
    while (str) {
        count++;
        str++;
        str = strchr(str, ch);
    }
    return count;
}


FT_INTERNAL
size_t wstrchr_count(const wchar_t *str, wchar_t ch)
{
    if (str == NULL)
        return 0;

    size_t count = 0;
    str = wcschr(str, ch);
    while (str) {
        count++;
        str++;
        str = wcschr(str, ch);
    }
    return count;
}


FT_INTERNAL
const char *str_n_substring_beg(const char *str, char ch_separator, size_t n)
{
    if (str == NULL)
        return NULL;

    if (n == 0)
        return str;

    str = strchr(str, ch_separator);
    --n;
    while (n > 0) {
        if (str == NULL)
            return NULL;
        --n;
        str++;
        str = strchr(str, ch_separator);
    }
    return str ? (str + 1) : NULL;
}


#ifdef FT_HAVE_WCHAR
FT_INTERNAL
const wchar_t *wstr_n_substring_beg(const wchar_t *str, wchar_t ch_separator, size_t n)
{
    if (str == NULL)
        return NULL;

    if (n == 0)
        return str;

    str = wcschr(str, ch_separator);
    --n;
    while (n > 0) {
        if (str == NULL)
            return NULL;
        --n;
        str++;
        str = wcschr(str, ch_separator);
    }
    return str ? (str + 1) : NULL;
}
#endif /* FT_HAVE_WCHAR */


FT_INTERNAL
void str_n_substring(const char *str, char ch_separator, size_t n, const char **begin, const char **end)
{
    const char *beg = str_n_substring_beg(str, ch_separator, n);
    if (beg == NULL) {
        *begin = NULL;
        *end = NULL;
        return;
    }

    const char *en = strchr(beg, ch_separator);
    if (en == NULL) {
        en = str + strlen(str);
    }

    *begin = beg;
    *end = en;
    return;
}


#ifdef FT_HAVE_WCHAR
FT_INTERNAL
void wstr_n_substring(const wchar_t *str, wchar_t ch_separator, size_t n, const wchar_t **begin, const wchar_t **end)
{
    const wchar_t *beg = wstr_n_substring_beg(str, ch_separator, n);
    if (beg == NULL) {
        *begin = NULL;
        *end = NULL;
        return;
    }

    const wchar_t *en = wcschr(beg, ch_separator);
    if (en == NULL) {
        en = str + wcslen(str);
    }

    *begin = beg;
    *end = en;
    return;
}
#endif /* FT_HAVE_WCHAR */


FT_INTERNAL
string_buffer_t *create_string_buffer(size_t number_of_chars, enum str_buf_type type)
{
    size_t sz = (number_of_chars) * (type == CharBuf ? sizeof(char) : sizeof(wchar_t));
    string_buffer_t *result = (string_buffer_t *)F_MALLOC(sizeof(string_buffer_t));
    if (result == NULL)
        return NULL;
    result->str.data = F_MALLOC(sz);
    if (result->str.data == NULL) {
        F_FREE(result);
        return NULL;
    }
    result->data_sz = sz;
    result->type = type;

    if (sz && type == CharBuf) {
        result->str.cstr[0] = '\0';
#ifdef FT_HAVE_WCHAR
    } else if (sz && type == WCharBuf) {
        result->str.wstr[0] = L'\0';
#endif /* FT_HAVE_WCHAR */
    }

    return result;
}


FT_INTERNAL
void destroy_string_buffer(string_buffer_t *buffer)
{
    if (buffer == NULL)
        return;
    F_FREE(buffer->str.data);
    buffer->str.data = NULL;
    F_FREE(buffer);
}

FT_INTERNAL
string_buffer_t *copy_string_buffer(const string_buffer_t *buffer)
{
    assert(buffer);
    string_buffer_t *result = create_string_buffer(buffer->data_sz, buffer->type);
    if (result == NULL)
        return NULL;
    switch (buffer->type) {
        case CharBuf:
            if (FT_IS_ERROR(fill_buffer_from_string(result, buffer->str.cstr))) {
                destroy_string_buffer(result);
                return NULL;
            }
            break;
#ifdef FT_HAVE_WCHAR
        case WCharBuf:
            if (FT_IS_ERROR(fill_buffer_from_wstring(result, buffer->str.wstr))) {
                destroy_string_buffer(result);
                return NULL;
            }
            break;
#endif /* FT_HAVE_WCHAR */
        default:
            destroy_string_buffer(result);
            return NULL;
    }
    return result;
}

FT_INTERNAL
fort_status_t realloc_string_buffer_without_copy(string_buffer_t *buffer)
{
    assert(buffer);
    char *new_str = (char *)F_MALLOC(buffer->data_sz * 2);
    if (new_str == NULL) {
        return FT_MEMORY_ERROR;
    }
    F_FREE(buffer->str.data);
    buffer->str.data = new_str;
    buffer->data_sz *= 2;
    return FT_SUCCESS;
}


FT_INTERNAL
fort_status_t fill_buffer_from_string(string_buffer_t *buffer, const char *str)
{
    assert(buffer);
    assert(str);

    char *copy = F_STRDUP(str);
    if (copy == NULL)
        return FT_MEMORY_ERROR;

    F_FREE(buffer->str.data);
    buffer->str.cstr = copy;
    buffer->type = CharBuf;

    return FT_SUCCESS;
}


#ifdef FT_HAVE_WCHAR
FT_INTERNAL
fort_status_t fill_buffer_from_wstring(string_buffer_t *buffer, const wchar_t *str)
{
    assert(buffer);
    assert(str);

    wchar_t *copy = F_WCSDUP(str);
    if (copy == NULL)
        return FT_MEMORY_ERROR;

    F_FREE(buffer->str.data);
    buffer->str.wstr = copy;
    buffer->type = WCharBuf;

    return FT_SUCCESS;
}
#endif /* FT_HAVE_WCHAR */


FT_INTERNAL
size_t buffer_text_height(const string_buffer_t *buffer)
{
    if (buffer == NULL || buffer->str.data == NULL || buf_str_len(buffer) == 0) {
        return 0;
    }
    if (buffer->type == CharBuf)
        return 1 + strchr_count(buffer->str.cstr, '\n');
    else
        return 1 + wstrchr_count(buffer->str.wstr, L'\n');
}


FT_INTERNAL
size_t buffer_text_width(const string_buffer_t *buffer)
{
    size_t max_length = 0;
    if (buffer->type == CharBuf) {
        size_t n = 0;
        while (1) {
            const char *beg = NULL;
            const char *end = NULL;
            str_n_substring(buffer->str.cstr, '\n', n, &beg, &end);
            if (beg == NULL || end == NULL)
                return max_length;

            max_length = MAX(max_length, (size_t)(end - beg));
            ++n;
        }
#ifdef FT_HAVE_WCHAR
    } else {
        size_t n = 0;
        while (1) {
            const wchar_t *beg = NULL;
            const wchar_t *end = NULL;
            wstr_n_substring(buffer->str.wstr, L'\n', n, &beg, &end);
            if (beg == NULL || end == NULL)
                return max_length;

            int line_width = mk_wcswidth(beg, (size_t)(end - beg));
            if (line_width < 0) /* For safety */
                line_width = 0;
            max_length = MAX(max_length, (size_t)line_width);

            ++n;
        }
#endif /* FT_HAVE_WCHAR */
    }

    return max_length; /* shouldn't be here */
}


FT_INTERNAL
int buffer_printf(string_buffer_t *buffer, size_t buffer_row, char *buf, size_t total_buf_len,
                  const context_t *context, const char *content_style_tag, const char *reset_content_style_tag)
{
#define CHAR_TYPE char
#define NULL_CHAR '\0'
#define NEWLINE_CHAR '\n'
#define SPACE_CHAR " "
#define SNPRINTF_FMT_STR "%*s"
#define SNPRINTF snprintf
#define BUFFER_STR str.cstr
#define SNPRINT_N_STRINGS  snprint_n_strings
#define STR_N_SUBSTRING str_n_substring
#define STR_ITER_WIDTH str_iter_width

    size_t buf_len = total_buf_len - strlen(content_style_tag) - strlen(reset_content_style_tag);

    if (buffer == NULL || buffer->str.data == NULL
        || buffer_row >= buffer_text_height(buffer) || buf_len == 0) {
        return -1;
    }

    size_t content_width = buffer_text_width(buffer);
    if ((buf_len - 1) < content_width)
        return -1;

    size_t left = 0;
    size_t right = 0;

    switch (get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_TEXT_ALIGN)) {
        case FT_ALIGNED_LEFT:
            left = 0;
            right = (buf_len - 1) - content_width;
            break;
        case FT_ALIGNED_CENTER:
            left = ((buf_len - 1) - content_width) / 2;
            right = ((buf_len - 1) - content_width) - left;
            break;
        case FT_ALIGNED_RIGHT:
            left = (buf_len - 1) - content_width;
            right = 0;
            break;
        default:
            assert(0);
            break;
    }

    int set_old_value = 0;
    size_t  written = 0;
    int tmp = 0;
    ptrdiff_t str_it_width = 0;
    const CHAR_TYPE *beg = NULL;
    const CHAR_TYPE *end = NULL;
    CHAR_TYPE old_value = (CHAR_TYPE)0;

    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINT_N_STRINGS(buf + written, total_buf_len - written, left, SPACE_CHAR));

    STR_N_SUBSTRING(buffer->BUFFER_STR, NEWLINE_CHAR, buffer_row, &beg, &end);
    if (beg == NULL || end == NULL)
        return -1;
    old_value = *end;
    *(CHAR_TYPE *)end = NULL_CHAR;
    set_old_value = 1;

    str_it_width = STR_ITER_WIDTH(beg, end);
    if (str_it_width < 0 || content_width < (size_t)str_it_width)
        goto  clear;

    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINT_N_STRINGS(buf + written, total_buf_len - written, 1, content_style_tag));
    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINTF(buf + written, total_buf_len - written, SNPRINTF_FMT_STR, (int)(end - beg), beg));
    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINT_N_STRINGS(buf + written, total_buf_len - written, 1, reset_content_style_tag));

    *(CHAR_TYPE *)end = old_value;
    set_old_value = 0;
    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINT_N_STRINGS(buf + written,  total_buf_len - written, (content_width - (size_t)str_it_width), SPACE_CHAR));
    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINT_N_STRINGS(buf + written, total_buf_len - written, right, SPACE_CHAR));
    return (int)written;

clear:
    if (set_old_value)
        *(CHAR_TYPE *)end = old_value;
    return -1;

#undef CHAR_TYPE
#undef NULL_CHAR
#undef NEWLINE_CHAR
#undef SPACE_CHAR
#undef SNPRINTF_FMT_STR
#undef SNPRINTF
#undef BUFFER_STR
#undef SNPRINT_N_STRINGS
#undef STR_N_SUBSTRING
#undef STR_ITER_WIDTH
}


#ifdef FT_HAVE_WCHAR
FT_INTERNAL
int buffer_wprintf(string_buffer_t *buffer, size_t buffer_row, wchar_t *buf, size_t total_buf_len,
                   const context_t *context, const char *content_style_tag, const char *reset_content_style_tag)
{
#define CHAR_TYPE wchar_t
#define NULL_CHAR L'\0'
#define NEWLINE_CHAR L'\n'
#define SPACE_CHAR " "
#define SNPRINTF_FMT_STR L"%*ls"
#define SNPRINTF swprintf
#define BUFFER_STR str.wstr
#define SNPRINT_N_STRINGS  wsnprint_n_string
#define STR_N_SUBSTRING wstr_n_substring
#define STR_ITER_WIDTH wcs_iter_width

    size_t buf_len = total_buf_len - strlen(content_style_tag) - strlen(reset_content_style_tag);

    if (buffer == NULL || buffer->str.data == NULL
        || buffer_row >= buffer_text_height(buffer) || buf_len == 0) {
        return -1;
    }

    size_t content_width = buffer_text_width(buffer);
    if ((buf_len - 1) < content_width)
        return -1;

    size_t left = 0;
    size_t right = 0;

    switch (get_cell_property_value_hierarcial(context->table_properties, context->row, context->column, FT_CPROP_TEXT_ALIGN)) {
        case FT_ALIGNED_LEFT:
            left = 0;
            right = (buf_len - 1) - content_width;
            break;
        case FT_ALIGNED_CENTER:
            left = ((buf_len - 1) - content_width) / 2;
            right = ((buf_len - 1) - content_width) - left;
            break;
        case FT_ALIGNED_RIGHT:
            left = (buf_len - 1) - content_width;
            right = 0;
            break;
        default:
            assert(0);
            break;
    }

    int set_old_value = 0;
    size_t  written = 0;
    int tmp = 0;
    ptrdiff_t str_it_width = 0;
    const CHAR_TYPE *beg = NULL;
    const CHAR_TYPE *end = NULL;
    CHAR_TYPE old_value = (CHAR_TYPE)0;

    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINT_N_STRINGS(buf + written, total_buf_len - written, left, SPACE_CHAR));

    STR_N_SUBSTRING(buffer->BUFFER_STR, NEWLINE_CHAR, buffer_row, &beg, &end);
    if (beg == NULL || end == NULL)
        return -1;
    old_value = *end;
    *(CHAR_TYPE *)end = NULL_CHAR;
    set_old_value = 1;

    str_it_width = STR_ITER_WIDTH(beg, end);
    if (str_it_width < 0 || content_width < (size_t)str_it_width)
        goto  clear;

    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINT_N_STRINGS(buf + written, total_buf_len - written, 1, content_style_tag));
    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINTF(buf + written, total_buf_len - written, SNPRINTF_FMT_STR, (int)(end - beg), beg));
    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINT_N_STRINGS(buf + written, total_buf_len - written, 1, reset_content_style_tag));

    *(CHAR_TYPE *)end = old_value;
    set_old_value = 0;
    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINT_N_STRINGS(buf + written,  total_buf_len - written, (content_width - (size_t)str_it_width), SPACE_CHAR));
    CHCK_RSLT_ADD_TO_WRITTEN(SNPRINT_N_STRINGS(buf + written, total_buf_len - written, right, SPACE_CHAR));
    return (int)written;

clear:
    if (set_old_value)
        *(CHAR_TYPE *)end = old_value;
    return -1;

#undef CHAR_TYPE
#undef NULL_CHAR
#undef NEWLINE_CHAR
#undef SPACE_CHAR
#undef SNPRINTF_FMT_STR
#undef SNPRINTF
#undef BUFFER_STR
#undef SNPRINT_N_STRINGS
#undef STR_N_SUBSTRING
#undef STR_ITER_WIDTH
}
#endif /* FT_HAVE_WCHAR */


FT_INTERNAL
size_t string_buffer_capacity(const string_buffer_t *buffer)
{
    assert(buffer);
    if (buffer->type == CharBuf)
        return buffer->data_sz;
    else
        return buffer->data_sz / sizeof(wchar_t);
}


FT_INTERNAL
void *buffer_get_data(string_buffer_t *buffer)
{
    assert(buffer);
    return buffer->str.data;
}

/********************************************************
   End of file "string_buffer.c"
 ********************************************************/


/********************************************************
   Begin of file "table.c"
 ********************************************************/

/* #include "table.h" */ /* Commented by amalgamation script */
/* #include "string_buffer.h" */ /* Commented by amalgamation script */
/* #include "cell.h" */ /* Commented by amalgamation script */
/* #include "vector.h" */ /* Commented by amalgamation script */
/* #include "row.h" */ /* Commented by amalgamation script */

FT_INTERNAL
separator_t *create_separator(int enabled)
{
    separator_t *res = (separator_t *)F_CALLOC(1, sizeof(separator_t));
    if (res == NULL)
        return NULL;
    res->enabled = enabled;
    return res;
}


FT_INTERNAL
void destroy_separator(separator_t *sep)
{
    F_FREE(sep);
}


FT_INTERNAL
separator_t *copy_separator(separator_t *sep)
{
    assert(sep);
    return create_separator(sep->enabled);
}


static
fort_row_t *get_row_implementation(ft_table_t *table, size_t row, enum PolicyOnNull policy)
{
    if (table == NULL || table->rows == NULL) {
        return NULL;
    }

    switch (policy) {
        case DoNotCreate:
            if (row < vector_size(table->rows)) {
                return *(fort_row_t **)vector_at(table->rows, row);
            }
            return NULL;
        case Create:
            while (row >= vector_size(table->rows)) {
                fort_row_t *new_row = create_row();
                if (new_row == NULL)
                    return NULL;
                if (FT_IS_ERROR(vector_push(table->rows, &new_row))) {
                    destroy_row(new_row);
                    return NULL;
                }
            }
            return *(fort_row_t **)vector_at(table->rows, row);
    }

    assert(0 && "Shouldn't be here!");
    return NULL;
}


FT_INTERNAL
fort_row_t *get_row(ft_table_t *table, size_t row)
{
    return get_row_implementation(table, row, DoNotCreate);
}


FT_INTERNAL
const fort_row_t *get_row_c(const ft_table_t *table, size_t row)
{
    return get_row((ft_table_t *)table, row);
}


FT_INTERNAL
fort_row_t *get_row_and_create_if_not_exists(ft_table_t *table, size_t row)
{
    return get_row_implementation(table, row, Create);
}


FT_INTERNAL
string_buffer_t *get_cur_str_buffer_and_create_if_not_exists(ft_table_t *table)
{
    assert(table);

    fort_row_t *row = get_row_and_create_if_not_exists(table, table->cur_row);
    if (row == NULL)
        return NULL;
    fort_cell_t *cell = get_cell_and_create_if_not_exists(row, table->cur_col);
    if (cell == NULL)
        return NULL;

    return cell_get_string_buffer(cell);
}


/*
 * Returns number of cells (rows * cols)
 */
FT_INTERNAL
fort_status_t get_table_sizes(const ft_table_t *table, size_t *rows, size_t *cols)
{
    *rows = 0;
    *cols = 0;
    if (table && table->rows) {
        *rows = vector_size(table->rows);
        /*
        fort_row_t *row = NULL;
        FOR_EACH(fort_row_t *, row, table->rows) {
            (void)i0;
            size_t cols_in_row = columns_in_row(row);
            if (cols_in_row > *cols)
                *cols = cols_in_row;
        }
        */
        size_t row_index = 0;
        for (row_index = 0; row_index < vector_size(table->rows); ++row_index) {
            fort_row_t *row = *(fort_row_t **)vector_at(table->rows, row_index);
            size_t cols_in_row = columns_in_row(row);
            if (cols_in_row > *cols)
                *cols = cols_in_row;
        }
    }
    return FT_SUCCESS;
}


FT_INTERNAL
fort_status_t table_rows_and_cols_geometry(const ft_table_t *table,
        size_t **col_width_arr_p, size_t *col_width_arr_sz,
        size_t **row_height_arr_p, size_t *row_height_arr_sz,
        enum request_geom_type geom)
{
    if (table == NULL) {
        return FT_ERROR;
    }



    size_t cols = 0;
    size_t rows = 0;
    int status = get_table_sizes(table, &rows, &cols);
    if (FT_IS_ERROR(status))
        return status;

    size_t *col_width_arr = (size_t *)F_CALLOC(sizeof(size_t), cols);
    size_t *row_height_arr = (size_t *)F_CALLOC(sizeof(size_t), rows);
    if (col_width_arr == NULL || row_height_arr == NULL) {
        F_FREE(col_width_arr);
        F_FREE(row_height_arr);
        return FT_ERROR;
    }

    int combined_cells_found = 0;
    context_t context;
    context.table_properties = (table->properties ? table->properties : &g_table_properties);
    size_t col = 0;
    for (col = 0; col < cols; ++col) {
        col_width_arr[col] = 0;
        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) {
                switch (get_cell_type(cell)) {
                    case CommonCell:
                        col_width_arr[col] = MAX(col_width_arr[col], hint_width_cell(cell, &context, geom));
                        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));
            } else {
                size_t cell_empty_string_height = get_cell_property_value_hierarcial(context.table_properties, context.row, context.column, FT_CPROP_EMPTY_STR_HEIGHT);
                if (cell_empty_string_height) {
                    size_t cell_top_padding = get_cell_property_value_hierarcial(context.table_properties, context.row, context.column, FT_CPROP_TOP_PADDING);
                    size_t cell_bottom_padding = get_cell_property_value_hierarcial(context.table_properties, context.row, context.column, FT_CPROP_BOTTOM_PADDING);
                    row_height_arr[row] = MAX(row_height_arr[row], cell_empty_string_height + cell_top_padding + cell_bottom_padding);
                }
            }
        }
    }

    if (combined_cells_found) {
        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, geom);
                        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
     */
    /*
    if (table->properties) {
        for (size_t i = 0; i < cols; ++i) {
            col_width_arr[i] = MAX((int)col_width_arr[i], fort_props_column_width(table->properties, i));
        }
    }
    */

    *col_width_arr_p = col_width_arr;
    *col_width_arr_sz = cols;
    *row_height_arr_p = row_height_arr;
    *row_height_arr_sz = rows;
    return FT_SUCCESS;
}


/*
 * Returns geometry in characters
 */
FT_INTERNAL
fort_status_t table_geometry(const ft_table_t *table, size_t *height, size_t *width)
{
    if (table == NULL)
        return FT_ERROR;

    *height = 0;
    *width = 0;
    size_t cols = 0;
    size_t rows = 0;
    size_t *col_width_arr = NULL;
    size_t *row_height_arr = NULL;

    int status = table_rows_and_cols_geometry(table, &col_width_arr, &cols, &row_height_arr, &rows, INTERN_REPR_GEOMETRY);
    if (FT_IS_ERROR(status))
        return status;

    *width = 1 + (cols == 0 ? 1 : cols) + 1; /* for boundaries (that take 1 symbol) + newline   */
    size_t i = 0;
    for (i = 0; i < cols; ++i) {
        *width += col_width_arr[i];
    }

    /* todo: add check for non printable horizontal row separators */
    *height = 1 + (rows == 0 ? 1 : rows); /* for boundaries (that take 1 symbol)  */
    for (i = 0; i < rows; ++i) {
        *height += row_height_arr[i];
    }
    F_FREE(col_width_arr);
    F_FREE(row_height_arr);

    if (table->properties) {
        *height += table->properties->entire_table_properties.top_margin;
        *height += table->properties->entire_table_properties.bottom_margin;
        *width += table->properties->entire_table_properties.left_margin;
        *width += table->properties->entire_table_properties.right_margin;
    }

    /* Take into account that border elements can be more than one byte long */
    fort_table_properties_t *table_properties = table->properties ? table->properties : &g_table_properties;
    size_t max_border_elem_len = max_border_elem_strlen(table_properties);
    *width *= max_border_elem_len;

    return FT_SUCCESS;

}


/********************************************************
   End of file "table.c"
 ********************************************************/


/********************************************************
   Begin of file "vector.c"
 ********************************************************/

/* #include "vector.h" */ /* Commented by amalgamation script */
#include <assert.h>
#include <string.h>

struct vector {
    size_t m_size;
    void  *m_data;
    size_t m_capacity;
    size_t m_item_size;
};


static int vector_reallocate_(vector_t *vector, size_t new_capacity)
{
    assert(vector);
    assert(new_capacity > vector->m_capacity);

    size_t new_size = new_capacity * vector->m_item_size;
    vector->m_data = F_REALLOC(vector->m_data, new_size);
    if (vector->m_data == NULL)
        return -1;
    return 0;
}


FT_INTERNAL
vector_t *create_vector(size_t item_size, size_t capacity)
{
    vector_t *vector = (vector_t *)F_MALLOC(sizeof(vector_t));
    if (vector == NULL) {
        SYS_LOG_ERROR("Failed to allocate memory for asock vector");
        return NULL;
    }

    size_t init_size = MAX(item_size * capacity, 1);
    vector->m_data = F_MALLOC(init_size);
    if (vector->m_data == NULL) {
        SYS_LOG_ERROR("Failed to allocate memory for asock vector inern. buffer");
        F_FREE(vector);
        return NULL;
    }

    vector->m_size      = 0;
    vector->m_capacity  = capacity;
    vector->m_item_size = item_size;

    return vector;
}


FT_INTERNAL
void destroy_vector(vector_t *vector)
{
    assert(vector);
    F_FREE(vector->m_data);
    F_FREE(vector);
}


FT_INTERNAL
size_t vector_size(const vector_t *vector)
{
    assert(vector);
    return vector->m_size;
}


FT_INTERNAL
size_t vector_capacity(const vector_t *vector)
{
    assert(vector);
    return vector->m_capacity;
}


FT_INTERNAL
int vector_push(vector_t *vector, const void *item)
{
    assert(vector);
    assert(item);

    if (vector->m_size == vector->m_capacity) {
        if (vector_reallocate_(vector, vector->m_capacity * 2) == -1)
            return FT_ERROR;
        vector->m_capacity = vector->m_capacity * 2;
    }

    size_t offset = vector->m_size * vector->m_item_size;
    memcpy((char *)vector->m_data + offset, item, vector->m_item_size);

    ++(vector->m_size);

    return FT_SUCCESS;
}


FT_INTERNAL
const void *vector_at_c(const vector_t *vector, size_t index)
{
    if (index >= vector->m_size)
        return NULL;

    return (char *)vector->m_data + index * vector->m_item_size;
}


FT_INTERNAL
void *vector_at(vector_t *vector, size_t index)
{
    if (index >= vector->m_size)
        return NULL;

    return (char *)vector->m_data + index * vector->m_item_size;
}


FT_INTERNAL
fort_status_t vector_swap(vector_t *cur_vec, vector_t *mv_vec, size_t pos)
{
    assert(cur_vec);
    assert(mv_vec);
    assert(cur_vec != mv_vec);
    assert(cur_vec->m_item_size == mv_vec->m_item_size);

    size_t cur_sz = vector_size(cur_vec);
    size_t mv_sz = vector_size(mv_vec);
    if (mv_sz == 0) {
        return FT_SUCCESS;
    }

    size_t min_targ_size = pos + mv_sz;
    if (vector_capacity(cur_vec) < min_targ_size) {
        if (vector_reallocate_(cur_vec, min_targ_size) == -1)
            return FT_ERROR;
        cur_vec->m_capacity = min_targ_size;
    }

    size_t offset = pos * cur_vec->m_item_size;
    void *tmp = NULL;
    size_t new_mv_sz = 0;
    if (cur_sz > pos) {
        new_mv_sz = MIN(cur_sz - pos, mv_sz);
        tmp = F_MALLOC(cur_vec->m_item_size * new_mv_sz);
        if (tmp == NULL) {
            return FT_MEMORY_ERROR;
        }
    }

    if (tmp) {
        memcpy(tmp,
               (char *)cur_vec->m_data + offset,
               cur_vec->m_item_size * new_mv_sz);
    }

    memcpy((char *)cur_vec->m_data + offset,
           mv_vec->m_data,
           cur_vec->m_item_size * mv_sz);

    if (tmp) {
        memcpy(mv_vec->m_data,
               tmp,
               cur_vec->m_item_size * new_mv_sz);
    }

    cur_vec->m_size = MAX(cur_vec->m_size, min_targ_size);
    mv_vec->m_size = new_mv_sz;
    F_FREE(tmp);
    return FT_SUCCESS;
}


#ifdef FT_TEST_BUILD

vector_t *copy_vector(vector_t *v)
{
    if (v == NULL)
        return NULL;

    vector_t *new_vector = create_vector(v->m_item_size, v->m_capacity);
    if (new_vector == NULL)
        return NULL;

    memcpy(new_vector->m_data, v->m_data, v->m_item_size * v->m_size);
    new_vector->m_size      = v->m_size ;
    new_vector->m_item_size = v->m_item_size ;
    return new_vector;
}

size_t vector_index_of(const vector_t *vector, const void *item)
{
    assert(vector);
    assert(item);

    size_t i = 0;
    for (i = 0; i < vector->m_size; ++i) {
        void *data_pos = (char *)vector->m_data + i * vector->m_item_size;
        if (memcmp(data_pos, item, vector->m_item_size) == 0) {
            return i;
        }
    }
    return INVALID_VEC_INDEX;
}


int vector_erase(vector_t *vector, size_t index)
{
    assert(vector);

    if (vector->m_size == 0 || index >= vector->m_size)
        return FT_ERROR;

    memmove((char *)vector->m_data + vector->m_item_size * index,
            (char *)vector->m_data + vector->m_item_size * (index + 1),
            (vector->m_size - 1 - index) * vector->m_item_size);
    vector->m_size--;
    return FT_SUCCESS;
}


void vector_clear(vector_t *vector)
{
    vector->m_size = 0;
}
#endif

/********************************************************
   End of file "vector.c"
 ********************************************************/


/********************************************************
   Begin of file "wcwidth.c"
 ********************************************************/

/*
 * This is an implementation of wcwidth() and wcswidth() (defined in
 * IEEE Std 1002.1-2001) for Unicode.
 *
 * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
 * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
 *
 * In fixed-width output devices, Latin characters all occupy a single
 * "cell" position of equal width, whereas ideographic CJK characters
 * occupy two such cells. Interoperability between terminal-line
 * applications and (teletype-style) character terminals using the
 * UTF-8 encoding requires agreement on which character should advance
 * the cursor by how many cell positions. No established formal
 * standards exist at present on which Unicode character shall occupy
 * how many cell positions on character terminals. These routines are
 * a first attempt of defining such behavior based on simple rules
 * applied to data provided by the Unicode Consortium.
 *
 * For some graphical characters, the Unicode standard explicitly
 * defines a character-cell width via the definition of the East Asian
 * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
 * In all these cases, there is no ambiguity about which width a
 * terminal shall use. For characters in the East Asian Ambiguous (A)
 * class, the width choice depends purely on a preference of backward
 * compatibility with either historic CJK or Western practice.
 * Choosing single-width for these characters is easy to justify as
 * the appropriate long-term solution, as the CJK practice of
 * displaying these characters as double-width comes from historic
 * implementation simplicity (8-bit encoded characters were displayed
 * single-width and 16-bit ones double-width, even for Greek,
 * Cyrillic, etc.) and not any typographic considerations.
 *
 * Much less clear is the choice of width for the Not East Asian
 * (Neutral) class. Existing practice does not dictate a width for any
 * of these characters. It would nevertheless make sense
 * typographically to allocate two character cells to characters such
 * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
 * represented adequately with a single-width glyph. The following
 * routines at present merely assign a single-cell width to all
 * neutral characters, in the interest of simplicity. This is not
 * entirely satisfactory and should be reconsidered before
 * establishing a formal standard in this area. At the moment, the
 * decision which Not East Asian (Neutral) characters should be
 * represented by double-width glyphs cannot yet be answered by
 * applying a simple rule from the Unicode database content. Setting
 * up a proper standard for the behavior of UTF-8 character terminals
 * will require a careful analysis not only of each Unicode character,
 * but also of each presentation form, something the author of these
 * routines has avoided to do so far.
 *
 * http://www.unicode.org/unicode/reports/tr11/
 *
 * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
 *
 * Permission to use, copy, modify, and distribute this software
 * for any purpose and without fee is hereby granted. The author
 * disclaims all warranties with regard to this software.
 *
 * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
 */

/* #include "wcwidth.h" */ /* Commented by amalgamation script */

#ifdef FT_HAVE_WCHAR


struct interval {
    wchar_t first;
    wchar_t last;
};

/* auxiliary function for binary search in interval table */
static int bisearch(wchar_t ucs, const struct interval *table, int max)
{
    int min = 0;

    if (ucs < table[0].first || ucs > table[max].last)
        return 0;
    while (max >= min) {
        int mid = (min + max) / 2;
        if (ucs > table[mid].last)
            min = mid + 1;
        else if (ucs < table[mid].first)
            max = mid - 1;
        else
            return 1;
    }

    return 0;
}


/* The following two functions define the column width of an ISO 10646
 * character as follows:
 *
 *    - The null character (U+0000) has a column width of 0.
 *
 *    - Other C0/C1 control characters and DEL will lead to a return
 *      value of -1.
 *
 *    - Non-spacing and enclosing combining characters (general
 *      category code Mn or Me in the Unicode database) have a
 *      column width of 0.
 *
 *    - SOFT HYPHEN (U+00AD) has a column width of 1.
 *
 *    - Other format characters (general category code Cf in the Unicode
 *      database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
 *
 *    - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
 *      have a column width of 0.
 *
 *    - Spacing characters in the East Asian Wide (W) or East Asian
 *      Full-width (F) category as defined in Unicode Technical
 *      Report #11 have a column width of 2.
 *
 *    - All remaining characters (including all printable
 *      ISO 8859-1 and WGL4 characters, Unicode control characters,
 *      etc.) have a column width of 1.
 *
 * This implementation assumes that wchar_t characters are encoded
 * in ISO 10646.
 */

static int mk_wcwidth(wchar_t ucs)
{
    /* sorted list of non-overlapping intervals of non-spacing characters */
    /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
    static const struct interval combining[] = {
        { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
        { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
        { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
        { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
        { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
        { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
        { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
        { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
        { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
        { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
        { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
        { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
        { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
        { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
        { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
        { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
        { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
        { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
        { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
        { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
        { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
        { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
        { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
        { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
        { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
        { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
        { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
        { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
        { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
        { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
        { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
        { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
        { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
        { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
        { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
        { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
        { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
        { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
        { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
        { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
        { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
        { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
        { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
        { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
        { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
        { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
        { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
        { 0xE0100, 0xE01EF }
    };

    /* test for 8-bit control characters */
    if (ucs == 0)
        return 0;
    if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
        return -1;

    /* binary search in table of non-spacing characters */
    if (bisearch(ucs, combining,
                 sizeof(combining) / sizeof(struct interval) - 1))
        return 0;

    /* if we arrive here, ucs is not a combining or C0/C1 control character */

    return 1 +
           (ucs >= 0x1100 &&
            (ucs <= 0x115f ||                    /* Hangul Jamo init. consonants */
             ucs == 0x2329 || ucs == 0x232a ||
             (ucs >= 0x2e80 && ucs <= 0xa4cf &&
              ucs != 0x303f) ||                  /* CJK ... Yi */
             (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
             (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
             (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
             (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
             (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
             (ucs >= 0xffe0 && ucs <= 0xffe6) ||
             (ucs >= 0x20000 && ucs <= 0x2fffd) ||
             (ucs >= 0x30000 && ucs <= 0x3fffd)));
}


FT_INTERNAL
int mk_wcswidth(const wchar_t *pwcs, size_t n)
{
    int width = 0;

    for (; *pwcs && n-- > 0; pwcs++) {
        int w;
        if ((w = mk_wcwidth(*pwcs)) < 0)
            return -1;
        else
            width += w;
    }

    return width;
}
#endif /* FT_HAVE_WCHAR */

/********************************************************
   End of file "wcwidth.c"
 ********************************************************/

