ANSI Printing Library/Collection - afmt

Most of what I do involves outputting to the terminal. Over the years I’ve used ANSI frequently to colorize my output. Even though I’m very familiar with ANSI, I still found that I was always needing to reference the codes and the correct order of things. Then of course came the debugging of fat fingered sequences, etc.

So I’ve done several versions of procedures that will do the ANSI for me, but I was still never satisfied. I’d still have to remember what I named the procedures, and ultimately found myself just wishing that the standard print procedures would support some kind of shorthand functionality for ANSI.

Well, I finally created such a thing. afmt mirrors many of the print procedures in fmt. The ones that are currently supported work exactly the same as expected if no ANSI format is provided. If an ANSI format definition is provided, it is intercepted, applied, and then passed on to the corresponding print procedure in fmt.

afmt is designed to be used as a collection. Place an “afmt” folder inside your collection folder or use the default collection folder “…/odin/shared”. Then place afmt.odin in that folder. Also Place the “examples” folder inside “afmt” folder, and then place “examples.odin” inside that folder.

To use the collection, it can be imported with:

import "shared:afmt"
// or
import "YourCollectionFolderName:afmt"

https://github.com/OnlyXuul/afmt

// change "odin/shared" to location of your collection folder
cd odin/shared
git clone https://github.com/OnlyXuul/afmt.git
cd afmt/examples
odin run .

Supported procedures:

  • print, println, printf, printfln
  • tprint, tprintln, tprintf, tprintfln
  • aprint, aprintln, aprintf, aprintfln
  • bprint, bprintln, bprintf, bprintfln
  • sbprint, sbprintln, sbprintf, sbprintfln
  • ctprint, ctprintf, ctprintfln (ctprintln not in fmt, so it’s currently excluded)
  • caprint, caprintf, caprintfln (caprintln not in fmt, so it’s currently excluded)

I provide an examples.odin file which details the usage. The following is found at the beginning of that file:

	//	All print procedures in afmt work the same as their respective fmt version when not using ANSI.
	//	If using ANSI, then the ANSI format is the first arg of the ..args variatic parameter for each procedure. i.e. arg[0]
	//	Then the remaining args are passed to the appropriate procedure. i.e. ..args[1:]
	//	There are two ways to define an ANSI format.
	//		1. Using ANSI_Format struct, which has 4 variants. ANSI_3Bit, ANSI_4Bit, ANSI_8Bit, and ANSI_24Bit.
	//		2. Using a single string:
	//			ANSI_4Bit  -> "-f[blue] -b[black] -a[bold, underline]"
	//			ANSI_8Bit  -> "-f[255] -b[50] -a[bold, underline]"
	//			ANSI_24Bit -> "-f[200, 220, 250] -b[0, 0, 0] -a[bold, underline]"
	//
	//	Rules:
	//		- Cannot combine color types between foreground and background in the same ANSI format definition.
	//			i.e. cannot do "-f[blue] -b[0, 0, 0]". This combines 4bit with 24bit.
	//		- Attributes are independant of foreground and background colors and will be applied even if color definitions are invalid.
	//		- Not all fields must be set. Fields not set are ignored, and the terminal will use it's default.
	//
	//	Notes: If you are using a terminal with a custom theme defined when using 3bit, or 4bit, the colors will be converted by your terminal
	//	to the theme's version of those colors. afmt has no control over this. To over-ride themes, use either 8bit or 24bit colors.
	//	afmt applies standard ANSI sequences. The accuracy of output depends on your terminal's support. If an ANSI sequence is not
	//	supported, the terminal should ignore it.

If there is a desire for it, support for panicf, ensuref, and assertf, could easily be added.

A screenshot from examples.odin

6 Likes

Thanks for the likes. I really do hope others enjoy using this as much as I do. :slight_smile:

Big Update:

All files updated, including examples.odin

New file: colors.odin

  • add this to root of afmt
  • Provides named colors (should be same or similar to HTML color names)
  • Usable only with afmt.RGB (i.e. afmt.ANSI_24Bit)

Procedures Added

  • hsl_rgb - hsl to rgb utility
  • rgb_hsl - rgb to hsl utility
  • hsl_rgb666 - hsl to base-6 [3]u8 rgb utility (for 8Bit colors)
  • rgb666_hsl - base-6 [3]u8 rgb to hsl utility (for 8bit colors)
  • rgb666_to_8bit - convert base-6 [3]u8 to 8bit (16-231)
  • rgb666_from_8bit - convert 8bit (16-231) to base-6 [3]u8
  • print_3bit_color_test
  • print_4bit_color_test
  • print_8bit_color_test
  • print_24bit_color_test
  • print_8bit_color_spectrum_bar
  • print_24bit_color_spectrum_bar - same as print_24bit_color_test which is the brute force method kept for testing hsl used by spectrum_bar(s)
  • color_name_from_value - utility for working with color names from colors.odin
  • color_value_from_name - utility for working with color names from colors.odin

1 Like

I’ve made some updates worth noting here.

There is one code change that affects existing projects (if there are any). I’ll help anyone with making updates to their code if needed.

This change felt necessary. At the beginning I was trying to maintain swizzling, but in the end, that was never possible with a nil-able array (doi). It makes more sense that either the whole color is nil, or it is completely defined, instead of the old way of each individual color (R or G or B) being nil-able. This change simplifies type assertions a bit, but adds the requirement for 24 bit color literals to be defined as either ‘[3]u8’ or ‘afmt.RGB’.

// changed from
RGB :: distinct [3]Maybe(u8)
// to
RGB :: [3]u8

ANSI24 (previously named ANSI_24Bit)
// now uses the following for colors
ANSI24 :: struct {
	fg: Maybe(RGB),
	bg: Maybe(RGB),
	at: bit_set[Attribute],
}

Structure name changes. These all have aliases to the old names. After making this change and updating the examples, things felt much cleaner. Hope you agree.

ANSI_Format   renamed to ANSI
ANSI_3Bit     renamed to ANSI3
ANSI_4Bit     renamed to ANSI4
ANSI_8Bit     renamed to ANSI8
ANSI_24Bit    renamed to ANSI24
FG_Color_3Bit renamed to FGColor3
BG_Color_3Bit renamed to BGColor3
FG_Color_4Bit renamed to FGColor4
BG_Color_4Bit renamed to BGColor4

Procedure name changes. These feel more symmetrical and self explanatory.

hsl // overload of the next 2
hsl_to_rgb
hsl_from_rgb

hsl666 // overload of the next 2
hsl_to_rgb666
hsl_from_rgb666

rgb666 // overload of the next 2
rgb666_to_8bit
rgb666_from_8bit

New procedures and structure

Column :: struct($V: typeid) where intrinsics.type_is_variant_of(ANSI, V) {
	width:   u8,
	justify: enum {LEFT, CENTER, RIGHT},
	ansi:    V,
}

// Prints row of N Column(s)
// Must provide an array of Column(s)
// See examples.odin for usage

//	Overload: print row by slice or ..any
printrow :: proc {printrow_slice, printrow_any}

printrow_slice :: proc(row: [$N]$V/Column, slice: []$T)
printrow_any :: proc(row: [$N]$V/Column, args: ..any)

// Some color utilities

relative_luminance ::proc(rgb: RGB)
contrast_ratio :: proc(c1, c2: RGB)
print_color_name_guide :: proc(group := "all")
2 Likes

Thanks for all your effort and good work!