Skip to content

Commit

Permalink
More box chars
Browse files Browse the repository at this point in the history
  • Loading branch information
kovidgoyal committed Dec 22, 2024
1 parent 3468c2d commit 446c37d
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 38 deletions.
145 changes: 145 additions & 0 deletions kitty/decorations.c
Original file line number Diff line number Diff line change
Expand Up @@ -1051,6 +1051,118 @@ mid_lines(Canvas *self, uint level, ...) {
va_end(args);
}

static Point*
get_fading_lines(uint total_length, uint num, Edge fade) {
uint step = total_length / num, d1 = 0; int dir = 1;
if (fade == LEFT_EDGE || fade == TOP_EDGE) { dir = -1; d1 = total_length; }
Point *ans = malloc(num * sizeof(Point));
if (!ans) fatal("Out of memory");
for (uint i = 0; i < num; i++) {
uint sz = step * (num - i) / (num + 1);
if (step > 2 && sz >= step - 1) sz = step - 2;
int d2 = d1 + dir * sz; if (d2 < 0) d2 = 0;
if (d1 <= (uint)d2) { ans[i].x = d1; ans[i].y = d2; }
else { ans[i].x = d2; ans[i].y = d1; }
d1 += step * dir;
}
return ans;
}

static void
fading_hline(Canvas *self, uint level, uint num, Edge fade) {
uint y = self->height / 2;
RAII_ALLOC(Point, pts, get_fading_lines(self->width, num, fade));
for (uint i = 0; i < num; i++) {
uint x1 = pts[i].x, x2 = pts[i].y;
draw_hline(self, x1, x2, y, level);
}
}

static void
fading_vline(Canvas *self, uint level, uint num, Edge fade) {
uint x = self->width / 2;
RAII_ALLOC(Point, pts, get_fading_lines(self->height, num, fade));
for (uint i = 0; i < num; i++) {
uint y1 = pts[i].x, y2 = pts[i].y;
draw_vline(self, y1, y2, x, level);
}
}

typedef struct Rectircle Rectircle;
typedef double (*Rectircle_equation)(Rectircle r, double t);

typedef struct Rectircle {
uint a, b;
double yexp, xexp, adjust_x;
uint cell_width;
Rectircle_equation x, y;
} Rectircle;

static double
rectircle_lower_quadrant_y(Rectircle r, double t) {
return r.b * t; // 0 -> top of cell, 1 -> middle of cell
}

static double
rectircle_upper_quadrant_y(Rectircle r, double t) {
return r.b * (2. - t); // 0 -> bottom of cell, 1 -> middle of cell
}

// x(t). To get this we first need |y(t)|/b. This is just t since as t goes
// from 0 to 1 y goes from either 0 to b or 0 to -b

static double
rectircle_left_quadrant_x(Rectircle r, double t) {
double xterm = 1 - pow(t, r.yexp);
return floor(r.cell_width - fabs(r.a * pow(xterm, r.xexp)) - r.adjust_x);
}

static double
rectircle_right_quadrant_x(Rectircle r, double t) {
double xterm = 1 - pow(t, r.yexp);
return ceil(fabs(r.a * pow(xterm, r.xexp)));
}

static Rectircle
rectcircle(Canvas *self, Corner which) {
/*
Return two functions, x(t) and y(t) that map the parameter t which must be
in the range [0, 1] to x and y coordinates in the cell. The rectircle equation
we use is:
(|x| / a) ^ (2a / r) + (|y| / a) ^ (2b / r) = 1
where 2a = width, 2b = height and r is radius
The entire rectircle fits in four cells, each cell being one quadrant
of the full rectircle and the origin being the center of the rectircle.
The functions we return do the mapping for the specified cell.
╭╮
╰╯
See https://math.stackexchange.com/questions/1649714
*/
double radius = self->width / 2.;
uint cell_width_is_odd = (self->width / self->supersample_factor) & 1;
Rectircle ans = {
.a = ((self->width / self->supersample_factor) / 2) * self->supersample_factor,
.b = ((self->height / self->supersample_factor) / 2) * self->supersample_factor,
.yexp = self->height / radius,
.xexp = radius / self->width,
.cell_width = self->width,
.adjust_x = cell_width_is_odd * self->supersample_factor,
.x = which & LEFT_EDGE ? rectircle_left_quadrant_x : rectircle_right_quadrant_x,
.y = which & TOP_EDGE ? rectircle_upper_quadrant_y : rectircle_lower_quadrant_y,
};

return ans;
}

static void
rounded_corner(Canvas *self, uint level, Corner which) {
Rectircle r = rectcircle(self, which);
draw_parametrized_curve(self, level, r.x(r, t), r.y(r, t));
}

void
render_box_char(char_type ch, uint8_t *buf, unsigned width, unsigned height, double dpi_x, double dpi_y) {
Canvas canvas = {.mask=buf, .width = width, .height = height, .dpi={.x=dpi_x, .y=dpi_y}, .supersample_factor=1u}, ss = canvas;
Expand Down Expand Up @@ -1326,6 +1438,39 @@ render_box_char(char_type ch, uint8_t *buf, unsigned width, unsigned height, dou
S(L'🮬', mid_lines, 1, TOP_RIGHT, TOP_LEFT, BOTTOM_RIGHT, 0);
S(L'🮭', mid_lines, 1, TOP_RIGHT, TOP_LEFT, BOTTOM_LEFT, 0);
S(L'🮮', mid_lines, 1, TOP_RIGHT, BOTTOM_RIGHT, TOP_LEFT, BOTTOM_LEFT, 0);

C(L'', hline, 1);
C(L'', vline, 1);
C(L'', fading_hline, 1, 4, RIGHT_EDGE);
C(L'', fading_hline, 1, 4, LEFT_EDGE);
C(L'', fading_vline, 1, 5, BOTTOM_EDGE);
C(L'', fading_vline, 1, 5, TOP_EDGE);

S(L'', rounded_corner, 1, TOP_LEFT);
S(L'', rounded_corner, 1, TOP_RIGHT);
S(L'', rounded_corner, 1, BOTTOM_LEFT);
S(L'', rounded_corner, 1, BOTTOM_RIGHT);

SS(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT));
SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT));
SS(L'', rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, TOP_LEFT));
SS(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_RIGHT));
SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_RIGHT));
SS(L'', rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_RIGHT));
SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT));
SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_LEFT));
SS(L'', rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, TOP_RIGHT));
SS(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_RIGHT));
SS(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT));
SS(L'', rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
SS(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, TOP_RIGHT));
SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_RIGHT));
SS(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, TOP_LEFT));
SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_LEFT));
SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_LEFT));
}
free(canvas.holes); free(canvas.y_limits);
free(ss.holes); free(ss.y_limits);
Expand Down
81 changes: 43 additions & 38 deletions kitty/fonts/box_drawing.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ def draw_vline(buf: BufType, width: int, y1: int, y2: int, x: int, level: int, s
buf[x + y * width] = 255


def half_hline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'left', extend_by: int = 0) -> None:
def half_hline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'left', extend_by: int = 0, ssf: int = 1) -> None:
x1, x2 = (0, extend_by + width // 2) if which == 'left' else (width // 2 - extend_by, width)
draw_hline(buf, width, height, x1, x2, height // 2, level)
draw_hline(buf, width, height, x1, x2, height // 2, level, supersample_factor=ssf)


def half_vline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'top', extend_by: int = 0) -> None:
def half_vline(buf: BufType, width: int, height: int, level: int = 1, which: str = 'top', extend_by: int = 0, ssf: int = 1) -> None:
y1, y2 = (0, height // 2 + extend_by) if which == 'top' else (height // 2 - extend_by, height)
draw_vline(buf, width, y1, y2, width // 2, level)
draw_vline(buf, width, y1, y2, width // 2, level, supersample_factor=ssf)


def get_holes(sz: int, hole_sz: int, num: int) -> list[tuple[int, ...]]:
Expand Down Expand Up @@ -104,15 +104,20 @@ def add_vholes(buf: BufType, width: int, height: int, level: int = 1, num: int =
buf[x + width * y] = 0


def hline(buf: BufType, width: int, height: int, level: int = 1) -> None:
half_hline(buf, width, height, level=level)
half_hline(buf, width, height, level=level, which='right')
def hline(buf: BufType, width: int, height: int, level: int = 1, ssf: int = 1) -> None:
half_hline(buf, width, height, level=level, ssf=ssf)
half_hline(buf, width, height, level=level, which='right', ssf=ssf)


def vline(buf: BufType, width: int, height: int, level: int = 1) -> None:
half_vline(buf, width, height, level=level)
half_vline(buf, width, height, level=level, which='bottom')
def vline(buf: BufType, width: int, height: int, level: int = 1, ssf: int = 1) -> None:
half_vline(buf, width, height, level=level, ssf=ssf)
half_vline(buf, width, height, level=level, which='bottom', ssf=ssf)

def svline(buf: BufType, width: int, height: int, level: int = 1) -> None:
vline(buf, width, height, level, getattr(buf, 'supersample_factor'))

def shline(buf: BufType, width: int, height: int, level: int = 1) -> None:
hline(buf, width, height, level, getattr(buf, 'supersample_factor'))

def hholes(buf: BufType, width: int, height: int, level: int = 1, num: int = 1) -> None:
hline(buf, width, height, level=level)
Expand Down Expand Up @@ -178,8 +183,7 @@ class SSByteArray(bytearray):
supersample_factor = 1


def ss(buf: BufType, width: int, height: int, *funcs: Callable[..., None]) -> None:
supersample_factor = getattr(funcs[0], 'supersample_factor')
def ss(buf: BufType, width: int, height: int, supersample_factor: int, *funcs: Callable[..., None]) -> None:
w, h = supersample_factor * width, supersample_factor * height
ssbuf = SSByteArray(w * h)
ssbuf.supersample_factor = supersample_factor
Expand Down Expand Up @@ -366,20 +370,16 @@ def get_fading_lines(total_length: int, num: int = 1, fade: str = 'right') -> It
d1 += step * dir


@supersampled()
def fading_hline(buf: SSByteArray, width: int, height: int, level: int = 1, num: int = 1, fade: str = 'right') -> None:
factor = buf.supersample_factor
y = (height // 2 // factor) * factor
y = height // 2
for x1, x2 in get_fading_lines(width, num, fade):
draw_hline(buf, width, height, x1, x2, y, level, supersample_factor = factor)
draw_hline(buf, width, height, x1, x2, y, level)


@supersampled()
def fading_vline(buf: SSByteArray, width: int, height: int, level: int = 1, num: int = 1, fade: str = 'down') -> None:
factor = buf.supersample_factor
x = (width // 2 // factor) * factor
x = width // 2
for y1, y2 in get_fading_lines(height, num, fade):
draw_vline(buf, width, y1, y2, x, level, supersample_factor = factor)
draw_vline(buf, width, y1, y2, x, level)


ParameterizedFunc = Callable[[float], float]
Expand Down Expand Up @@ -1346,26 +1346,26 @@ def braille(buf: BufType, width: int, height: int, which: int = 0) -> None:
'': [p(rounded_corner, which='╮')],
'': [p(rounded_corner, which='╰')],
'': [p(rounded_corner, which='╯')],
'': [vline, p(rounded_corner, which='╰')],
'': [vline, p(rounded_corner, which='╭')],
'': [svline, p(rounded_corner, which='╰')],
'': [svline, p(rounded_corner, which='╭')],
'': [p(rounded_corner, which='╰'), p(rounded_corner, which='╭')],
'': [vline, p(rounded_corner, which='╯')],
'': [vline, p(rounded_corner, which='╮')],
'': [svline, p(rounded_corner, which='╯')],
'': [svline, p(rounded_corner, which='╮')],
'': [p(rounded_corner, which='╮'), p(rounded_corner, which='╯')],
'': [hline, p(rounded_corner, which='╮')],
'': [hline, p(rounded_corner, which='╭')],
'': [shline, p(rounded_corner, which='╮')],
'': [shline, p(rounded_corner, which='╭')],
'': [p(rounded_corner, which='╭'), p(rounded_corner, which='╮')],
'': [hline, p(rounded_corner, which='╯')],
'': [hline, p(rounded_corner, which='╰')],
'': [shline, p(rounded_corner, which='╯')],
'': [shline, p(rounded_corner, which='╰')],
'': [p(rounded_corner, which='╰'), p(rounded_corner, which='╯')],
'': [vline, p(rounded_corner, which='╰'), p(rounded_corner, which='╯')],
'': [vline, p(rounded_corner, which='╭'), p(rounded_corner, which='╮')],
'': [hline, p(rounded_corner, which='╮'), p(rounded_corner, which='╯')],
'': [hline, p(rounded_corner, which='╰'), p(rounded_corner, which='╭')],
'': [vline, p(rounded_corner, which='╭'), p(rounded_corner, which='╯')],
'': [vline, p(rounded_corner, which='╮'), p(rounded_corner, which='╰')],
'': [hline, p(rounded_corner, which='╭'), p(rounded_corner, which='╯')],
'': [hline, p(rounded_corner, which='╮'), p(rounded_corner, which='╰')],
'': [svline, p(rounded_corner, which='╰'), p(rounded_corner, which='╯')],
'': [svline, p(rounded_corner, which='╭'), p(rounded_corner, which='╮')],
'': [shline, p(rounded_corner, which='╮'), p(rounded_corner, which='╯')],
'': [shline, p(rounded_corner, which='╰'), p(rounded_corner, which='╭')],
'': [svline, p(rounded_corner, which='╭'), p(rounded_corner, which='╯')],
'': [svline, p(rounded_corner, which='╮'), p(rounded_corner, which='╰')],
'': [shline, p(rounded_corner, which='╭'), p(rounded_corner, which='╯')],
'': [shline, p(rounded_corner, which='╮'), p(rounded_corner, which='╰')],
'': [commit],
'': [p(commit, solid=False)],
'': [p(commit, lines=['right'])],
Expand Down Expand Up @@ -1445,8 +1445,13 @@ def render_box_char(ch: str, buf: BufType, width: int, height: int, dpi: float =
global _dpi
_dpi = dpi
funcs = box_chars[ch]
if hasattr(funcs[0], 'supersample_factor'):
ss(buf, width, height, *funcs)
ssf = 0
for x in funcs:
if hasattr(x, 'supersample_factor'):
ssf = getattr(x, 'supersample_factor')
break
if ssf:
ss(buf, width, height, ssf, *funcs)
else:
for func in box_chars[ch]:
func(buf, width, height)
Expand Down

0 comments on commit 446c37d

Please sign in to comment.