mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-14 04:24:52 +02:00
This adds a config option called text_fg_override_threshold that specifies a percentage difference luminance below which the foreground color will be overridden. The foreground color is set to white if the background is dark or black if the background is light. The default is 0, and the computations will only be performed if the option is set. Many programs output colors that look good with the author's terminal's color scheme but which are completely illegible with other color schemes. This allows the user ensure that there is always sufficient contrast to read the text on the screen. I originally implemented this is as a parameter on text_composition_strategy. For that to work, the option needed to take _up to_ rather than _exactly_ the number of available parameters. While it now has nothing to do with the new feature, it seems like that change should be made anyway, so I'm leaving it in for now.
270 lines
8.9 KiB
C
270 lines
8.9 KiB
C
/*
|
|
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
|
|
*
|
|
* Distributed under terms of the GPL3 license.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "../state.h"
|
|
#include "../colors.h"
|
|
|
|
static inline float
|
|
PyFloat_AsFloat(PyObject *o) {
|
|
return (float)PyFloat_AsDouble(o);
|
|
}
|
|
|
|
static inline color_type
|
|
color_as_int(PyObject *color) {
|
|
if (!PyObject_TypeCheck(color, &Color_Type)) { PyErr_SetString(PyExc_TypeError, "Not a Color object"); return 0; }
|
|
Color *c = (Color*)color;
|
|
return c->color.val & 0xffffff;
|
|
}
|
|
|
|
static inline color_type
|
|
color_or_none_as_int(PyObject *color) {
|
|
if (color == Py_None) return 0;
|
|
return color_as_int(color);
|
|
}
|
|
|
|
static inline color_type
|
|
active_border_color(PyObject *color) {
|
|
if (color == Py_None) return 0x00ff00;
|
|
return color_as_int(color);
|
|
}
|
|
|
|
|
|
static inline monotonic_t
|
|
parse_s_double_to_monotonic_t(PyObject *val) {
|
|
return s_double_to_monotonic_t(PyFloat_AsDouble(val));
|
|
}
|
|
|
|
static inline monotonic_t
|
|
parse_ms_long_to_monotonic_t(PyObject *val) {
|
|
return ms_to_monotonic_t(PyLong_AsUnsignedLong(val));
|
|
}
|
|
|
|
static WindowTitleIn
|
|
window_title_in(PyObject *title_in) {
|
|
const char *in = PyUnicode_AsUTF8(title_in);
|
|
switch(in[0]) {
|
|
case 'a': return ALL;
|
|
case 'w': return WINDOW;
|
|
case 'm': return MENUBAR;
|
|
case 'n': return NONE;
|
|
default: break;
|
|
}
|
|
return ALL;
|
|
}
|
|
|
|
static BackgroundImageLayout
|
|
bglayout(PyObject *layout_name) {
|
|
const char *name = PyUnicode_AsUTF8(layout_name);
|
|
switch(name[0]) {
|
|
case 't': return TILING;
|
|
case 'm': return MIRRORED;
|
|
case 's': return SCALED;
|
|
case 'c': {
|
|
return name[1] == 'l' ? CLAMPED : CENTER_CLAMPED;
|
|
}
|
|
default: break;
|
|
}
|
|
return TILING;
|
|
}
|
|
|
|
static ImageAnchorPosition
|
|
bganchor(PyObject *anchor_name) {
|
|
const char *name = PyUnicode_AsUTF8(anchor_name);
|
|
ImageAnchorPosition anchor = {0.5f, 0.5f, 0.5f, 0.5f};
|
|
if (strstr(name, "top") != NULL) {
|
|
anchor.canvas_y = 0.f; anchor.image_y = 0.f;
|
|
} else if (strstr(name, "bottom") != NULL) {
|
|
anchor.canvas_y = 1.f; anchor.image_y = 1.f;
|
|
}
|
|
if (strstr(name, "left") != NULL) {
|
|
anchor.canvas_x = 0.f; anchor.image_x = 0.f;
|
|
} else if (strstr(name, "right") != NULL) {
|
|
anchor.canvas_x = 1.f; anchor.image_x = 1.f;
|
|
}
|
|
return anchor;
|
|
}
|
|
|
|
#define STR_SETTER(name) { \
|
|
free(opts->name); opts->name = NULL; \
|
|
if (src == Py_None || !PyUnicode_Check(src)) return; \
|
|
Py_ssize_t sz; \
|
|
const char *s = PyUnicode_AsUTF8AndSize(src, &sz); \
|
|
opts->name = calloc(sz + 1, 1); \
|
|
if (opts->name) memcpy(opts->name, s, sz); \
|
|
}
|
|
|
|
static void
|
|
background_image(PyObject *src, Options *opts) { STR_SETTER(background_image); }
|
|
|
|
static void
|
|
bell_path(PyObject *src, Options *opts) { STR_SETTER(bell_path); }
|
|
|
|
static void
|
|
bell_theme(PyObject *src, Options *opts) { STR_SETTER(bell_theme); }
|
|
|
|
|
|
static void
|
|
window_logo_path(PyObject *src, Options *opts) { STR_SETTER(default_window_logo); }
|
|
|
|
#undef STR_SETTER
|
|
|
|
static void
|
|
parse_font_mod_size(PyObject *val, float *sz, AdjustmentUnit *unit) {
|
|
PyObject *mv = PyObject_GetAttrString(val, "mod_value");
|
|
if (mv) {
|
|
*sz = PyFloat_AsFloat(PyTuple_GET_ITEM(mv, 0));
|
|
long u = PyLong_AsLong(PyTuple_GET_ITEM(mv, 1));
|
|
switch (u) { case POINT: case PERCENT: case PIXEL: *unit = u; break; }
|
|
}
|
|
}
|
|
|
|
static void
|
|
modify_font(PyObject *mf, Options *opts) {
|
|
#define S(which) { PyObject *v = PyDict_GetItemString(mf, #which); if (v) parse_font_mod_size(v, &opts->which.val, &opts->which.unit); }
|
|
S(underline_position); S(underline_thickness); S(strikethrough_thickness); S(strikethrough_position);
|
|
S(cell_height); S(cell_width); S(baseline);
|
|
#undef S
|
|
}
|
|
|
|
static MouseShape
|
|
pointer_shape(PyObject *shape_name) {
|
|
const char *name = PyUnicode_AsUTF8(shape_name);
|
|
switch(name[0]) {
|
|
case 'a': return ARROW;
|
|
case 'h': return HAND;
|
|
case 'b': return BEAM;
|
|
default: break;
|
|
}
|
|
return BEAM;
|
|
}
|
|
|
|
static int
|
|
macos_colorspace(PyObject *csname) {
|
|
if (PyUnicode_CompareWithASCIIString(csname, "srgb") == 0) return 1;
|
|
if (PyUnicode_CompareWithASCIIString(csname, "displayp3") == 0) return 2;
|
|
return 0;
|
|
}
|
|
|
|
static inline void
|
|
free_url_prefixes(void) {
|
|
OPT(url_prefixes).num = 0;
|
|
OPT(url_prefixes).max_prefix_len = 0;
|
|
if (OPT(url_prefixes).values) {
|
|
free(OPT(url_prefixes.values));
|
|
OPT(url_prefixes).values = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
url_prefixes(PyObject *up, Options *opts) {
|
|
if (!PyTuple_Check(up)) { PyErr_SetString(PyExc_TypeError, "url_prefixes must be a tuple"); return; }
|
|
free_url_prefixes();
|
|
opts->url_prefixes.values = calloc(PyTuple_GET_SIZE(up), sizeof(UrlPrefix));
|
|
if (!opts->url_prefixes.values) { PyErr_NoMemory(); return; }
|
|
opts->url_prefixes.num = PyTuple_GET_SIZE(up);
|
|
for (size_t i = 0; i < opts->url_prefixes.num; i++) {
|
|
PyObject *t = PyTuple_GET_ITEM(up, i);
|
|
if (!PyUnicode_Check(t)) { PyErr_SetString(PyExc_TypeError, "url_prefixes must be strings"); return; }
|
|
opts->url_prefixes.values[i].len = MIN(arraysz(opts->url_prefixes.values[i].string) - 1, (size_t)PyUnicode_GET_LENGTH(t));
|
|
int kind = PyUnicode_KIND(t);
|
|
opts->url_prefixes.max_prefix_len = MAX(opts->url_prefixes.max_prefix_len, opts->url_prefixes.values[i].len);
|
|
for (size_t x = 0; x < opts->url_prefixes.values[i].len; x++) {
|
|
opts->url_prefixes.values[i].string[x] = PyUnicode_READ(kind, PyUnicode_DATA(t), x);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
text_composition_strategy(PyObject *val, Options *opts) {
|
|
if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "text_rendering_strategy must be a string"); return; }
|
|
opts->text_old_gamma = false;
|
|
opts->text_gamma_adjustment = 1.0f; opts->text_contrast = 0.f;
|
|
if (PyUnicode_CompareWithASCIIString(val, "platform") == 0) {
|
|
#ifdef __APPLE__
|
|
opts->text_gamma_adjustment = 1.7f; opts->text_contrast = 30.f;
|
|
#endif
|
|
}
|
|
else if (PyUnicode_CompareWithASCIIString(val, "legacy") == 0) {
|
|
opts->text_old_gamma = true;
|
|
} else {
|
|
DECREF_AFTER_FUNCTION PyObject *parts = PyUnicode_Split(val, NULL, 2);
|
|
int size = PyList_GET_SIZE(parts);
|
|
if (size < 1 || 2 < size) { PyErr_SetString(PyExc_ValueError, "text_rendering_strategy must be of the form number:[number]"); return; }
|
|
|
|
if (size > 0) {
|
|
DECREF_AFTER_FUNCTION PyObject *ga = PyFloat_FromString(PyList_GET_ITEM(parts, 0));
|
|
if (PyErr_Occurred()) return;
|
|
opts->text_gamma_adjustment = MAX(0.01f, PyFloat_AsFloat(ga));
|
|
}
|
|
|
|
if (size > 1) {
|
|
DECREF_AFTER_FUNCTION PyObject *contrast = PyFloat_FromString(PyList_GET_ITEM(parts, 1));
|
|
if (PyErr_Occurred()) return;
|
|
opts->text_contrast = MAX(0.0f, PyFloat_AsFloat(contrast));
|
|
opts->text_contrast = MIN(100.0f, opts->text_contrast);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
text_fg_override_threshold(PyObject *val, Options *opts) {
|
|
if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "text_fg_override_threshold must be a string"); return; }
|
|
opts->text_fg_override_threshold = 0.f;
|
|
|
|
DECREF_AFTER_FUNCTION PyObject *text_fg_override_threshold = PyFloat_FromString(val);
|
|
if (PyErr_Occurred()) return;
|
|
opts->text_fg_override_threshold = MAX(0.f, PyFloat_AsFloat(text_fg_override_threshold));
|
|
opts->text_fg_override_threshold = MIN(100.f, PyFloat_AsFloat(text_fg_override_threshold));
|
|
|
|
}
|
|
|
|
static char_type*
|
|
list_of_chars(PyObject *chars) {
|
|
if (!PyUnicode_Check(chars)) { PyErr_SetString(PyExc_TypeError, "list_of_chars must be a string"); return NULL; }
|
|
char_type *ans = calloc(PyUnicode_GET_LENGTH(chars) + 1, sizeof(char_type));
|
|
if (ans) {
|
|
for (ssize_t i = 0; i < PyUnicode_GET_LENGTH(chars); i++) {
|
|
ans[i] = PyUnicode_READ(PyUnicode_KIND(chars), PyUnicode_DATA(chars), i);
|
|
}
|
|
}
|
|
return ans;
|
|
}
|
|
|
|
static void
|
|
url_excluded_characters(PyObject *chars, Options *opts) {
|
|
free(opts->url_excluded_characters);
|
|
opts->url_excluded_characters = list_of_chars(chars);
|
|
}
|
|
|
|
static void
|
|
select_by_word_characters(PyObject *chars, Options *opts) {
|
|
free(opts->select_by_word_characters);
|
|
opts->select_by_word_characters = list_of_chars(chars);
|
|
}
|
|
|
|
static void
|
|
select_by_word_characters_forward(PyObject *chars, Options *opts) {
|
|
free(opts->select_by_word_characters_forward);
|
|
opts->select_by_word_characters_forward = list_of_chars(chars);
|
|
}
|
|
|
|
static void
|
|
tab_bar_style(PyObject *val, Options *opts) {
|
|
opts->tab_bar_hidden = PyUnicode_CompareWithASCIIString(val, "hidden") == 0 ? true: false;
|
|
}
|
|
|
|
static void
|
|
tab_bar_margin_height(PyObject *val, Options *opts) {
|
|
if (!PyTuple_Check(val) || PyTuple_GET_SIZE(val) != 2) {
|
|
PyErr_SetString(PyExc_TypeError, "tab_bar_margin_height is not a 2-item tuple");
|
|
return;
|
|
}
|
|
opts->tab_bar_margin_height.outer = PyFloat_AsDouble(PyTuple_GET_ITEM(val, 0));
|
|
opts->tab_bar_margin_height.inner = PyFloat_AsDouble(PyTuple_GET_ITEM(val, 1));
|
|
}
|