More box chars

This commit is contained in:
Kovid Goyal
2024-12-22 14:26:48 +05:30
parent 0bd5ba50b6
commit 68167b9ddb
2 changed files with 188 additions and 38 deletions

View File

@@ -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;
@@ -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);

View File

@@ -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, ...]]:
@@ -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)
@@ -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
@@ -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]
@@ -1346,26 +1346,26 @@ box_chars: dict[str, list[Callable[[BufType, int, int], Any]]] = {
'': [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'])],
@@ -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)