# libfort prints formatted text tables from C and C++ without a dependency

> libfort is a small crossplatform C library for building formatted text tables in console output, integrated by dropping two files into your project, with border styles, per-cell alignment and padding, multi-line cells, UTF-8 and wide character support, plus C++11 wrappers. It suits a CLI tool that needs to print a table, and it is a formatting library rather than a data or UI framework.

**seleznevae/libfort** — C/C++ library to create formatted ASCII tables for console applications

- Repository: https://github.com/seleznevae/libfort
- Stars: 537 · Forks: 71
- Language: C
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/seleznevae-libfort

## Two files and one header

libfort is described as a simple crossplatform library to create formatted text tables, and the integration story is the first thing to understand. You add two files, fort.c and fort.h from the lib directory, to your C or C++ project and include the header in the source file where you will use the library functions.

```C
#include "fort.h"
```

For C++ projects that use a compiler with C++11 support and later, there are convenient C++ wrappers around the C functions in fort.hpp, so instead of fort.h you include the other header.

```CPP
#include "fort.hpp"
```

The reason this works is stated as a design goal: trivial integration, and therefore all source code files are amalgamated in only 2 files. There is no separate build step to wire into your project and no package manager dependency to resolve. The library is MIT licensed, the default branch is develop, and the last push to the repository was on 2026-10-14, with v0.5.2 released on 2026-10-09 after v0.5.1 on 2026-10-03 and v0.5.0 on 2026-10-02. That three-releases-in-one-week pattern says the project is in an active phase, which for a library that others vendor means you should expect interface churn across minor versions.

## The five step usage pattern

The README describes the common usage pattern for the C API as five steps, and knowing them makes the rest of the library legible. You create a table with ft_create_table. You fill it with data using ft_write_ln, the printf-like functions such as fr_ptrintf_ln, or ft_row_write. You modify basic table appearance with functions such as ft_set_cell_prop and ft_set_border_style. You convert the table to its string representation with ft_to_string. You destroy the table with ft_destroy_table. The order matters in one respect: the table object owns the strings you write into it, so the conversion step has to happen before the destroy step, and if your program is long lived and creates many tables, the destroy step is what keeps that from becoming a leak. The library also offers a number of functions to fill a table, adding content by adding separate cells, rows or using the printf like functions, and it supports multiple lines in cells and UTF-8 and wide characters.

## A complete table in C

The basic example is short enough to serve as the whole tutorial. You create the table, mark the first row as a header row type, write the header line and three data lines, print the string, and destroy the table.

```C
/* C API */
#include <stdio.h>
#include "fort.h"
int main(void)
{
    ft_table_t *table = ft_create_table();
    /* Set "header" type for the first row */
    ft_set_cell_prop(table, 0, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE, FT_ROW_HEADER);

    ft_write_ln(table, "N", "Driver", "Time", "Avg Speed");

    ft_write_ln(table, "1", "Ricciardo", "1:25.945", "222.128");
    ft_write_ln(table, "2", "Hamilton", "1:26.373", "221.027");
    ft_write_ln(table, "3", "Verstappen", "1:26.469", "220.782");

    printf("%s\n", ft_to_string(table));
    ft_destroy_table(table);
}
```

Two details are worth noticing. The header row is not a separate concept from the data rows; it is a cell property applied to row 0 across all columns, using FT_ANY_COLUMN as the column selector, so the same mechanism sets the row type, the text alignment or the padding for any cell in the table. And the table is built entirely in memory before anything is printed, so the output is a single string rather than a stream of writes, which means you can inspect, log or redirect it as one unit.

## The C++ wrapper and what the output looks like

The C++ API expresses the same operations as a stream insertion, with fort::header marking the header row and fort::endr ending a row.

```C++
/* C++ API */
#include <iostream>
#include "fort.hpp"
int main(void)
{
    fort::char_table table;
    table << fort::header
        << "N" << "Driver" << "Time" << "Avg Speed" << fort::endr
        << "1" << "Ricciardo" << "1:25.945" << "47.362" << fort::endr
        << "2" << "Hamilton" << "1:26.373" << "35.02" << fort::endr
        << "3" << "Verstappen" << "1:26.469" << "29.78" << fort::endr;

    std::cout << table.to_string() << std::endl;
}
```

The result is a plain ASCII table with light box drawing characters, and the README shows the exact expected output.

```text
+---+------------+----------+-----------+
| N | Driver     | Time     | Avg Speed |
+---+------------+----------+-----------+
| 1 | Ricciardo  | 1:25.945 | 47.362    |
| 2 | Hamilton   | 1:26.373 | 35.02     |
| 3 | Verstappen | 1:26.469 | 29.78     |
+---+------------+----------+-----------+
```

Notice that the columns are padded to the widest cell in the column, and the header row is separated by a rule because of the header row type. That alignment is the entire point of the library, and doing it by hand in printf is exactly the kind of code that breaks the moment a value changes width.

## Border styles and cell properties

Appearance is controlled from two directions. ft_set_border_style changes the border style of the table, with the README example switching to FT_DOUBLE2_STYLE, and the wiki documents a set of border styles in the C API pages. On the other side, ft_set_cell_prop sets a property for a cell, and the wiki has a page on cell and table properties covering indentation, alignment and padding. Alignment is applied per column in the README example, with FT_ANY_ROW as the row selector so the setting applies to the whole column.

```C
/* C API */
#include <stdio.h>
#include "fort.h"
int main(void)
{
    ft_table_t *table = ft_create_table();
    /* Change border style */
    ft_set_border_style(table, FT_DOUBLE2_STYLE);

    /* Set "header" type for the first row */
    ft_set_cell_prop(table, 0, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE, FT_ROW_HEADER);
    ft_write_ln(table, "Movie title", "Director", "Year", "Rating");

    ft_write_ln(table, "The Shawshank Redemption", "Frank Darabont", "1994", "9.5");
    ft_write_ln(table, "The Godfather", "Francis Ford Coppola", "1972", "9.2");
    ft_write_ln(table, "2001: A Space Odyssey", "Stanley Kubrick", "1968", "8.5");

    /* Set center alignment for the 1st and 3rd columns */
    ft_set_cell_prop(table, FT_ANY_ROW, 1, FT_CPROP_TEXT_ALIGN, FT_ALIGNED_CENTER);
    ft_set_cell_prop(table, FT_ANY_ROW, 3, FT_CPROP_TEXT_ALIGN, FT_ALIGNED_CENTER);

    printf("%s\n", ft_to_string(table));
    ft_destroy_table(table);
}
```

The C++ side mirrors the same operations with a chainable syntax, where the column method sets the alignment for one column.

```C++
    table.column(1).set_cell_text_align(fort::text_align::center);
```

The examples directory in the repository carries a numbered set of programs that cover this ground in both languages, including a complex layout example, a fill table example, a print styles example, a custom border style example, a math table, and a non-ASCII table, so the feature set is discoverable by reading the example list rather than the API documentation.

## Building with CMake

If libfort is installed on the host system, the README says it is sufficient to use find_package.

```cmake
find_package(libfort)
target_link_libraries(your_target PRIVATE libfort::fort)
```

If you downloaded the sources and embedded them in your project, for example by putting all sources in a directory called third-party/libfort, you use add_subdirectory instead.

```cmake
# Disable building tests and examples in libfort project
set(FORT_ENABLE_TESTING OFF CACHE INTERNAL "")

add_subdirectory(third-party/libfort)
target_link_libraries(your_target PRIVATE fort)
```

The first line of the second snippet is worth understanding rather than copying blindly. The library's own CMake project builds its tests and examples, and if you are vendoring the sources into your build you almost certainly do not want those targets, so the option is turned off before the directory is added. The repository itself carries a CMakeLists.txt, a cmake/ directory, a src/ directory, a lib/ directory with the amalgamated files, a tests/ directory, a docs/ directory and examples/, plus amalgamate.py, which is presumably how the two-file distribution is produced from the src/ tree. That last file answers a question you would otherwise have: the files you copy into your project are generated, and the sources they come from live in src/.

## The design goals and where libfort is the wrong tool

The README lists four design goals, and they explain the shape of the library. Portability: all main OSes, meaning Linux, Windows, macOS and FreeBSD, and compilers are supported. Maintainability: it is written in C because it is much less complicated than C++ and it can be used in both C and C++ projects and even on platforms without a C++ compiler. Heavy testing: the goal is to cover 100 percent of the code, and the README admits it is not reached yet, with tests run on all major compilers and platforms. That last line is the honest signal here, and the release cadence of three versions in a week is the other one. With four boundaries in mind, libfort is wrong when you need output that is not a terminal table. There is no CSV, Markdown, HTML or JSON rendering in the library, so if your CLI writes a report consumed by another program, you want a serialisation library rather than this one. There is no colour support in the table output beyond what the cell and border properties provide, so a CLI that wants ANSI colour in its table cells has to add that itself. There is no interactive terminal handling, no cursor control and no screen redrawing, so it is not a replacement for a terminal user interface library. And the printf-like write functions are the only formatting mechanism, so a table cell whose content needs wrapping to a fixed width, which is a common requirement for a report, is not something the library does for you. The reasonable alternative for the terminal case is a library that renders a table with colour and width handling, and for the file case, a table abstraction over a serialisation format, and libfort earns its place when you want bordered ASCII output from C with no dependencies and two files to copy.

## Conclusion

Adopt libfort if your C or C++ command line tool has to print aligned, bordered tables and you want more control than a handful of printf calls give you, because the library covers border styles, per cell alignment and padding, multi-line cells, UTF-8 and wide characters, and integrates as two source files with no build system of its own. Do not adopt it if you need to render a table into a file format, a terminal user interface with interactivity, or a colour scheme beyond what the border and cell properties expose. Verify two things first: that your toolchain can compile the library, since the design goal is portability across Linux, Windows, macOS and FreeBSD but the README does not list a minimum standard, and whether the printf-like write functions behave the way you expect with wide characters, since the library treats UTF-8 and wide characters as distinct paths. The practical first step is to copy fort.c and fort.h into your project, include fort.h, and run the simple table example to see the output shape before you start styling it.

## FAQ

### What is libfort?

It is a simple crossplatform C library for creating formatted text tables, with border styles, per cell alignment and padding properties, multiple lines per cell, and support for UTF-8 and wide characters. It is MIT licensed and can be used from C or C++.

### How do I integrate libfort into my project?

Add two files, fort.c and fort.h from the lib directory, to your project and include fort.h in the source where you use the library. C++11 or later projects can include fort.hpp instead for the C++ wrappers. The library also supports find_package(libfort) and add_subdirectory in CMake.

### What is the usual libfort usage pattern?

Create a table with ft_create_table, fill it with data using ft_write_ln, the printf-like functions or ft_row_write, modify the appearance with ft_set_cell_prop and ft_set_border_style, convert it to a string with ft_to_string, and destroy the table with ft_destroy_table.

### Which platforms does libfort support?

The README lists portability as a design goal and states that all main operating systems, Linux, Windows, macOS and FreeBSD, and compilers are supported. The library is written in C so it can be used in C and C++ projects and even on platforms without a C++ compiler.

### How well tested is libfort?

Heavy testing is one of the stated design goals, with the goal of covering 100 percent of the code and tests run on all major compilers and platforms. The README states plainly that the coverage goal is not reached yet, and the releases v0.5.0, v0.5.1 and v0.5.2 were published on 2026-10-02, 2026-10-03 and 2026-10-09.

## Sources

- [Issues](https://github.com/seleznevae/libfort/issues)
- [License: MIT](https://github.com/seleznevae/libfort/blob/develop/LICENSE)
- [README](https://github.com/seleznevae/libfort/blob/develop/README.md)
- [Releases](https://github.com/seleznevae/libfort/releases)
- [seleznevae/libfort on GitHub](https://github.com/seleznevae/libfort)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/seleznevae-libfort
