mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-10 19:29:37 +00:00
lib-vt: many more color utility APIs (#13206)
Embedders that render theme editors, palette pickers, or custom settings
UI need to use the same color semantics as Ghostty.
This moves the shared parsing paths into terminal/color and exposes them
through libghostty-vt. Config color and palette parsing now delegate to
the same helpers, so CLI/config behavior and the C ABI stay in lockstep.
From C:
GhosttyColorRgb rgb;
ghostty_color_parse("ForestGreen", 11, &rgb);
uint8_t index;
ghostty_color_parse_palette_entry(
"0x10=#282c34", 12, &index, &rgb);
const GhosttyColorX11Entry* names =
ghostty_color_x11_names();
The exported color API is:
ghostty_color_parse
ghostty_color_parse_x11
ghostty_color_parse_palette_entry
ghostty_color_palette_default
ghostty_color_palette_generate
ghostty_color_luminance
ghostty_color_perceived_luminance
ghostty_color_contrast
ghostty_color_x11_names
ghostty_color_x11_name_count
The X11 name table is parsed once at comptime into null-terminated
entries in rgb.txt order. The existing case-insensitive map keeps the
same behavior for RGB.parse and +list-colors, while bindings can walk a
static table without allocations.
This doesn't add any more binary size since all of this was already used
by terminal internals.
This commit is contained in:
@@ -7,6 +7,114 @@
|
||||
#ifndef GHOSTTY_VT_COLOR_H
|
||||
#define GHOSTTY_VT_COLOR_H
|
||||
|
||||
/** @defgroup color Color Utilities
|
||||
*
|
||||
* Color parsing, palette generation, color math, and X11 color name
|
||||
* utilities shared by libghostty-vt.
|
||||
*
|
||||
* These APIs expose Ghostty's color semantics directly to embedders. Use
|
||||
* them when an application needs to parse the same color strings as Ghostty
|
||||
* config and theme files, generate the same 256-color palette used by the
|
||||
* terminal, list supported X11 color names, or make UI decisions from
|
||||
* luminance and contrast values.
|
||||
*
|
||||
* ## Parsing Colors
|
||||
*
|
||||
* ghostty_color_parse() accepts the flexible syntax used by Ghostty for
|
||||
* terminal colors:
|
||||
*
|
||||
* - X11 color names, matched ASCII case-insensitively.
|
||||
* - 3- or 6-digit hex colors, with or without a leading `#`.
|
||||
* - 9- or 12-digit hex colors, with a leading `#`.
|
||||
* - XParseColor-style `rgb:<red>/<green>/<blue>` values.
|
||||
* - XParseColor-style `rgbi:<red>/<green>/<blue>` values.
|
||||
*
|
||||
* Leading and trailing spaces and tabs are ignored. Use
|
||||
* ghostty_color_parse_x11() when only X11 names should be accepted.
|
||||
*
|
||||
* @code{.c}
|
||||
* GhosttyColorRgb color;
|
||||
*
|
||||
* if (ghostty_color_parse(
|
||||
* "ForestGreen",
|
||||
* sizeof("ForestGreen") - 1,
|
||||
* &color) != GHOSTTY_SUCCESS) {
|
||||
* // Handle invalid color input.
|
||||
* }
|
||||
*
|
||||
* ghostty_color_parse("#abc", sizeof("#abc") - 1, &color);
|
||||
* ghostty_color_parse("rgb:12/34/56", sizeof("rgb:12/34/56") - 1, &color);
|
||||
* @endcode
|
||||
*
|
||||
* ## Palette Entries
|
||||
*
|
||||
* ghostty_color_parse_palette_entry() parses a single Ghostty palette
|
||||
* override in `INDEX=COLOR` form. The index may be decimal or use a `0x`,
|
||||
* `0o`, or `0b` prefix. The color side uses ghostty_color_parse().
|
||||
*
|
||||
* @code{.c}
|
||||
* GhosttyColorRgb palette[256];
|
||||
* ghostty_color_palette_default(palette);
|
||||
*
|
||||
* uint8_t index;
|
||||
* GhosttyColorRgb rgb;
|
||||
*
|
||||
* if (ghostty_color_parse_palette_entry(
|
||||
* "0x10=#282c34",
|
||||
* sizeof("0x10=#282c34") - 1,
|
||||
* &index,
|
||||
* &rgb) == GHOSTTY_SUCCESS) {
|
||||
* palette[index] = rgb;
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* ## Palette Generation
|
||||
*
|
||||
* ghostty_color_palette_generate() derives the 216-color cube and grayscale
|
||||
* ramp from a base palette, background, and foreground. Set bits in
|
||||
* GhosttyColorPaletteMask preserve specific indices from the base palette.
|
||||
* The output may alias the base input.
|
||||
*
|
||||
* @code{.c}
|
||||
* GhosttyColorRgb palette[256];
|
||||
* ghostty_color_palette_default(palette);
|
||||
*
|
||||
* GhosttyColorPaletteMask skip = {0};
|
||||
* GHOSTTY_COLOR_PALETTE_MASK_SET(&skip, 16);
|
||||
*
|
||||
* GhosttyColorRgb background = {40, 44, 52};
|
||||
* GhosttyColorRgb foreground = {220, 223, 228};
|
||||
*
|
||||
* ghostty_color_palette_generate(
|
||||
* palette,
|
||||
* &skip,
|
||||
* background,
|
||||
* foreground,
|
||||
* true,
|
||||
* palette);
|
||||
* @endcode
|
||||
*
|
||||
* ## X11 Color Names
|
||||
*
|
||||
* The X11 name table is static program-lifetime memory. Entries are in
|
||||
* rgb.txt order and are terminated by an entry with `name == NULL`.
|
||||
* ghostty_color_x11_name_count() returns the number of non-terminator
|
||||
* entries.
|
||||
*
|
||||
* @code{.c}
|
||||
* const GhosttyColorX11Entry* names = ghostty_color_x11_names();
|
||||
* size_t count = ghostty_color_x11_name_count();
|
||||
*
|
||||
* for (size_t i = 0; i < count; i++) {
|
||||
* // names[i].name and names[i].color are valid here.
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
|
||||
@@ -17,7 +125,7 @@ extern "C" {
|
||||
/**
|
||||
* RGB color value.
|
||||
*
|
||||
* @ingroup sgr
|
||||
* @ingroup color
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t r; /**< Red component (0-255) */
|
||||
@@ -28,49 +136,128 @@ typedef struct {
|
||||
/**
|
||||
* Palette color index (0-255).
|
||||
*
|
||||
* @ingroup sgr
|
||||
* @ingroup color
|
||||
*/
|
||||
typedef uint8_t GhosttyColorPaletteIndex;
|
||||
|
||||
/** @addtogroup sgr
|
||||
* @{
|
||||
/**
|
||||
* A 256-bit mask of palette indices.
|
||||
*
|
||||
* Index i is set iff `(bits[i >> 6] >> (i & 63)) & 1` is 1.
|
||||
* The mask is typically initialized to zero and then populated with
|
||||
* GHOSTTY_COLOR_PALETTE_MASK_SET().
|
||||
*
|
||||
* @code{.c}
|
||||
* GhosttyColorPaletteMask mask = {0};
|
||||
* GHOSTTY_COLOR_PALETTE_MASK_SET(&mask, 20);
|
||||
* if (GHOSTTY_COLOR_PALETTE_MASK_IS_SET(&mask, 20)) {
|
||||
* // Index 20 will be preserved.
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
typedef struct {
|
||||
uint64_t bits[4];
|
||||
} GhosttyColorPaletteMask;
|
||||
|
||||
/** Black color (0) @ingroup sgr */
|
||||
/**
|
||||
* An entry in Ghostty's X11 color name table.
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
typedef struct {
|
||||
/** Null-terminated color name. NULL marks the end of the table. */
|
||||
const char* name;
|
||||
/** The RGB value of the color. */
|
||||
GhosttyColorRgb color;
|
||||
} GhosttyColorX11Entry;
|
||||
|
||||
/**
|
||||
* Return the storage word for a palette mask index.
|
||||
*
|
||||
* @param index The palette index (0-255)
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
#define GHOSTTY_COLOR_PALETTE_MASK_WORD(index) ((index) >> 6)
|
||||
|
||||
/**
|
||||
* Return the storage bit for a palette mask index.
|
||||
*
|
||||
* @param index The palette index (0-255)
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
#define GHOSTTY_COLOR_PALETTE_MASK_BIT(index) (UINT64_C(1) << ((index) & 63))
|
||||
|
||||
/**
|
||||
* Set a palette mask index.
|
||||
*
|
||||
* @param mask Pointer to a GhosttyColorPaletteMask
|
||||
* @param index The palette index (0-255)
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
#define GHOSTTY_COLOR_PALETTE_MASK_SET(mask, index) \
|
||||
((mask)->bits[GHOSTTY_COLOR_PALETTE_MASK_WORD(index)] |= GHOSTTY_COLOR_PALETTE_MASK_BIT(index))
|
||||
|
||||
/**
|
||||
* Clear a palette mask index.
|
||||
*
|
||||
* @param mask Pointer to a GhosttyColorPaletteMask
|
||||
* @param index The palette index (0-255)
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
#define GHOSTTY_COLOR_PALETTE_MASK_UNSET(mask, index) \
|
||||
((mask)->bits[GHOSTTY_COLOR_PALETTE_MASK_WORD(index)] &= ~GHOSTTY_COLOR_PALETTE_MASK_BIT(index))
|
||||
|
||||
/**
|
||||
* Test whether a palette mask index is set.
|
||||
*
|
||||
* @param mask Pointer to a GhosttyColorPaletteMask
|
||||
* @param index The palette index (0-255)
|
||||
* @return true if the palette index is set, false otherwise
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
#define GHOSTTY_COLOR_PALETTE_MASK_IS_SET(mask, index) \
|
||||
(((mask)->bits[GHOSTTY_COLOR_PALETTE_MASK_WORD(index)] & GHOSTTY_COLOR_PALETTE_MASK_BIT(index)) != 0)
|
||||
|
||||
/** Black color (0) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_BLACK 0
|
||||
/** Red color (1) @ingroup sgr */
|
||||
/** Red color (1) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_RED 1
|
||||
/** Green color (2) @ingroup sgr */
|
||||
/** Green color (2) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_GREEN 2
|
||||
/** Yellow color (3) @ingroup sgr */
|
||||
/** Yellow color (3) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_YELLOW 3
|
||||
/** Blue color (4) @ingroup sgr */
|
||||
/** Blue color (4) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_BLUE 4
|
||||
/** Magenta color (5) @ingroup sgr */
|
||||
/** Magenta color (5) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_MAGENTA 5
|
||||
/** Cyan color (6) @ingroup sgr */
|
||||
/** Cyan color (6) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_CYAN 6
|
||||
/** White color (7) @ingroup sgr */
|
||||
/** White color (7) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_WHITE 7
|
||||
/** Bright black color (8) @ingroup sgr */
|
||||
/** Bright black color (8) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_BRIGHT_BLACK 8
|
||||
/** Bright red color (9) @ingroup sgr */
|
||||
/** Bright red color (9) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_BRIGHT_RED 9
|
||||
/** Bright green color (10) @ingroup sgr */
|
||||
/** Bright green color (10) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_BRIGHT_GREEN 10
|
||||
/** Bright yellow color (11) @ingroup sgr */
|
||||
/** Bright yellow color (11) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_BRIGHT_YELLOW 11
|
||||
/** Bright blue color (12) @ingroup sgr */
|
||||
/** Bright blue color (12) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_BRIGHT_BLUE 12
|
||||
/** Bright magenta color (13) @ingroup sgr */
|
||||
/** Bright magenta color (13) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_BRIGHT_MAGENTA 13
|
||||
/** Bright cyan color (14) @ingroup sgr */
|
||||
/** Bright cyan color (14) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_BRIGHT_CYAN 14
|
||||
/** Bright white color (15) @ingroup sgr */
|
||||
/** Bright white color (15) @ingroup color */
|
||||
#define GHOSTTY_COLOR_NAMED_BRIGHT_WHITE 15
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Get the RGB color components.
|
||||
*
|
||||
@@ -83,15 +270,209 @@ typedef uint8_t GhosttyColorPaletteIndex;
|
||||
* @param g Pointer to store the green component (0-255)
|
||||
* @param b Pointer to store the blue component (0-255)
|
||||
*
|
||||
* @ingroup sgr
|
||||
* @ingroup color
|
||||
*/
|
||||
GHOSTTY_API void ghostty_color_rgb_get(GhosttyColorRgb color,
|
||||
uint8_t* r,
|
||||
uint8_t* g,
|
||||
uint8_t* b);
|
||||
|
||||
/**
|
||||
* Parse an X11 color name.
|
||||
*
|
||||
* The color name is resolved from Ghostty's embedded rgb.txt table.
|
||||
* Leading and trailing spaces and tabs are trimmed, and matching is
|
||||
* ASCII case-insensitive. Hex values are not accepted by this function.
|
||||
*
|
||||
* @param name The color name bytes (must not be NULL)
|
||||
* @param len The length of @p name in bytes
|
||||
* @param[out] out The parsed RGB color
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if no color
|
||||
* matches or @p name is NULL
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_color_parse_x11(
|
||||
const char* name,
|
||||
size_t len,
|
||||
GhosttyColorRgb* out);
|
||||
|
||||
/**
|
||||
* Parse a flexible Ghostty color value.
|
||||
*
|
||||
* Accepts Ghostty's terminal color syntax: X11 color names, hex colors
|
||||
* in 3-, 6-, 9-, or 12-digit form (the leading # is optional for 3- and
|
||||
* 6-digit values), and rgb:<red>/<green>/<blue> or
|
||||
* rgbi:<red>/<green>/<blue> specifications. Leading and trailing spaces
|
||||
* and tabs are trimmed.
|
||||
*
|
||||
* @param value The color value bytes (must not be NULL)
|
||||
* @param len The length of @p value in bytes
|
||||
* @param[out] out The parsed RGB color
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if parsing
|
||||
* fails or @p value is NULL
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_color_parse(
|
||||
const char* value,
|
||||
size_t len,
|
||||
GhosttyColorRgb* out);
|
||||
|
||||
/**
|
||||
* Parse a Ghostty palette entry.
|
||||
*
|
||||
* Accepts Ghostty palette config syntax: N=COLOR. N is a palette index
|
||||
* from 0 to 255 in decimal or in 0x, 0o, or 0b-prefixed form. Spaces and
|
||||
* tabs around N and COLOR are ignored. COLOR accepts the same syntax as
|
||||
* ghostty_color_parse().
|
||||
*
|
||||
* @param value The palette entry bytes (must not be NULL)
|
||||
* @param len The length of @p value in bytes
|
||||
* @param[out] out_index The parsed palette index
|
||||
* @param[out] out_rgb The parsed RGB color
|
||||
* @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE on any
|
||||
* failure, including index overflow
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
GHOSTTY_API GhosttyResult ghostty_color_parse_palette_entry(
|
||||
const char* value,
|
||||
size_t len,
|
||||
uint8_t* out_index,
|
||||
GhosttyColorRgb* out_rgb);
|
||||
|
||||
/**
|
||||
* Get Ghostty's built-in default 256-color palette.
|
||||
*
|
||||
* Writes exactly 256 entries: Ghostty's base16 defaults, the xterm
|
||||
* 6x6x6 color cube, and the grayscale ramp.
|
||||
*
|
||||
* @param[out] out The output palette, an array of exactly 256
|
||||
* GhosttyColorRgb values
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
GHOSTTY_API void ghostty_color_palette_default(GhosttyColorRgb* out);
|
||||
|
||||
/**
|
||||
* Generate a 256-color palette from base colors.
|
||||
*
|
||||
* The base palette supplies indices 0-15, which are always preserved.
|
||||
* If @p base is NULL, Ghostty's default palette is used. If @p skip is
|
||||
* NULL, no extra indices are skipped. Set bits in @p skip preserve those
|
||||
* indices from @p base. The 216-color cube at indices 16-231 is generated
|
||||
* with trilinear CIELAB interpolation, and the grayscale ramp at indices
|
||||
* 232-255 is interpolated from the background to the foreground.
|
||||
*
|
||||
* For light themes, @p harmonious controls whether the generated palette
|
||||
* keeps the background-to-foreground orientation. When false, Ghostty
|
||||
* swaps the light background and dark foreground so the cube and ramp run
|
||||
* dark-to-light. The output palette may be the same pointer as @p base.
|
||||
*
|
||||
* @param base The base palette, an array of exactly 256 GhosttyColorRgb
|
||||
* values, or NULL to use Ghostty's default palette
|
||||
* @param skip The palette indices to preserve from @p base, or NULL for
|
||||
* an empty mask
|
||||
* @param bg The terminal background color
|
||||
* @param fg The terminal foreground color
|
||||
* @param harmonious Whether light themes keep background-to-foreground
|
||||
* orientation
|
||||
* @param[out] out The output palette, an array of exactly 256
|
||||
* GhosttyColorRgb values
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
GHOSTTY_API void ghostty_color_palette_generate(
|
||||
const GhosttyColorRgb* base,
|
||||
const GhosttyColorPaletteMask* skip,
|
||||
GhosttyColorRgb bg,
|
||||
GhosttyColorRgb fg,
|
||||
bool harmonious,
|
||||
GhosttyColorRgb* out);
|
||||
|
||||
/**
|
||||
* Calculate W3C relative luminance for an RGB color.
|
||||
*
|
||||
* Returns a normalized value from 0.0 for black to 1.0 for white.
|
||||
* See https://www.w3.org/TR/WCAG20/#relativeluminancedef.
|
||||
*
|
||||
* @param color The RGB color
|
||||
* @return Relative luminance in the range 0.0 to 1.0
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
GHOSTTY_API double ghostty_color_luminance(GhosttyColorRgb color);
|
||||
|
||||
/**
|
||||
* Calculate perceived luminance for an RGB color.
|
||||
*
|
||||
* Returns a normalized value from 0.0 for black to 1.0 for white.
|
||||
* Ghostty treats a background color as light when this exceeds 0.5.
|
||||
* This is not the metric used internally by
|
||||
* ghostty_color_palette_generate(), which uses CIELAB lightness.
|
||||
*
|
||||
* @param color The RGB color
|
||||
* @return Perceived luminance in the range 0.0 to 1.0
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
GHOSTTY_API double ghostty_color_perceived_luminance(GhosttyColorRgb color);
|
||||
|
||||
/**
|
||||
* Calculate the WCAG contrast ratio between two RGB colors.
|
||||
*
|
||||
* The contrast ratio is symmetric and ranges from 1.0 for identical
|
||||
* colors to 21.0 for black and white.
|
||||
*
|
||||
* @param a The first RGB color
|
||||
* @param b The second RGB color
|
||||
* @return WCAG contrast ratio in the range 1.0 to 21.0
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
GHOSTTY_API double ghostty_color_contrast(GhosttyColorRgb a, GhosttyColorRgb b);
|
||||
|
||||
/**
|
||||
* Get Ghostty's X11 color name table.
|
||||
*
|
||||
* The returned pointer references static memory valid for the program
|
||||
* lifetime and is never NULL. Entries are in rgb.txt order and are
|
||||
* terminated by an entry with name == NULL. Aliases are separate entries,
|
||||
* such as "medium spring green" and "MediumSpringGreen". Names are the
|
||||
* exact supported spellings from rgb.txt; ghostty_color_parse_x11() also
|
||||
* matches them case-insensitively.
|
||||
*
|
||||
* @code{.c}
|
||||
* for (const GhosttyColorX11Entry* e = ghostty_color_x11_names();
|
||||
* e->name != NULL;
|
||||
* e++) {
|
||||
* // e->name and e->color are valid here.
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @return Pointer to the first X11 color entry
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
GHOSTTY_API const GhosttyColorX11Entry* ghostty_color_x11_names(void);
|
||||
|
||||
/**
|
||||
* Get the number of X11 color name entries.
|
||||
*
|
||||
* The returned count excludes the NULL terminator and is provided so
|
||||
* bindings can preallocate storage before reading ghostty_color_x11_names().
|
||||
*
|
||||
* @return Number of X11 color name entries
|
||||
*
|
||||
* @ingroup color
|
||||
*/
|
||||
GHOSTTY_API size_t ghostty_color_x11_name_count(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif /* GHOSTTY_VT_COLOR_H */
|
||||
|
||||
@@ -19,8 +19,11 @@
|
||||
* The parser processes SGR parameters from CSI sequences (e.g., `ESC[1;31m`)
|
||||
* and returns individual text attributes like bold, italic, colors, etc.
|
||||
* It supports both semicolon (`;`) and colon (`:`) separators, possibly mixed,
|
||||
* and handles various color formats including 8-color, 16-color, 256-color,
|
||||
* X11 named colors, and RGB in multiple formats.
|
||||
* and handles SGR color attributes including 8-color, 16-color, 256-color,
|
||||
* direct RGB, underline color, and reset forms. Color values are returned
|
||||
* using the shared @ref color types; applications that need to parse Ghostty
|
||||
* config/theme color strings, generate palettes, inspect X11 color names, or
|
||||
* calculate luminance and contrast should use the @ref color APIs directly.
|
||||
*
|
||||
* ## Basic Usage
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user