mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-07-10 09:43:26 +00:00
start work on porting box drawing to C
This commit is contained in:
parent
ec3a6cc26e
commit
fdb9b17943
9 changed files with 614 additions and 61 deletions
|
|
@ -8,6 +8,11 @@
|
|||
#include "decorations.h"
|
||||
#include "state.h"
|
||||
|
||||
typedef uint32_t uint;
|
||||
|
||||
static uint max(uint a, uint b) { return a > b ? a : b; }
|
||||
static uint min(uint a, uint b) { return a < b ? a : b; }
|
||||
|
||||
#define STRAIGHT_UNDERLINE_LOOP \
|
||||
unsigned half = fcm.underline_thickness / 2; \
|
||||
DecorationGeometry ans = {.top = half > fcm.underline_position ? 0 : fcm.underline_position - half}; \
|
||||
|
|
@ -35,8 +40,8 @@ add_strikethrough(uint8_t *buf, FontCellMetrics fcm) {
|
|||
DecorationGeometry
|
||||
add_missing_glyph(uint8_t *buf, FontCellMetrics fcm) {
|
||||
DecorationGeometry ans = {.height=fcm.cell_height};
|
||||
unsigned thickness = MIN(fcm.underline_thickness, fcm.strikethrough_thickness);
|
||||
thickness = MIN(thickness, fcm.cell_width);
|
||||
unsigned thickness = min(fcm.underline_thickness, fcm.strikethrough_thickness);
|
||||
thickness = min(thickness, fcm.cell_width);
|
||||
for (unsigned y = 0; y < ans.height; y++) {
|
||||
uint8_t *line = buf + fcm.cell_width * y;
|
||||
if (y < thickness || y >= ans.height - thickness) memset(line, 0xff, fcm.cell_width);
|
||||
|
|
@ -51,9 +56,9 @@ add_missing_glyph(uint8_t *buf, FontCellMetrics fcm) {
|
|||
DecorationGeometry
|
||||
add_double_underline(uint8_t *buf, FontCellMetrics fcm) {
|
||||
unsigned a = fcm.underline_position > fcm.underline_thickness ? fcm.underline_position - fcm.underline_thickness : 0;
|
||||
a = MIN(a, fcm.cell_height - 1);
|
||||
unsigned b = MIN(fcm.underline_position, fcm.cell_height - 1);
|
||||
unsigned top = MIN(a, b), bottom = MAX(a, b);
|
||||
a = min(a, fcm.cell_height - 1);
|
||||
unsigned b = min(fcm.underline_position, fcm.cell_height - 1);
|
||||
unsigned top = min(a, b), bottom = max(a, b);
|
||||
int deficit = 2 - (bottom - top);
|
||||
if (deficit > 0) {
|
||||
if (bottom + deficit < fcm.cell_height) bottom += deficit;
|
||||
|
|
@ -62,8 +67,8 @@ add_double_underline(uint8_t *buf, FontCellMetrics fcm) {
|
|||
if (deficit > 1) top -= deficit - 1;
|
||||
} else top -= deficit;
|
||||
}
|
||||
top = MAX(0u, MIN(top, fcm.cell_height - 1u));
|
||||
bottom = MAX(0u, MIN(bottom, fcm.cell_height - 1u));
|
||||
top = max(0u, min(top, fcm.cell_height - 1u));
|
||||
bottom = max(0u, min(bottom, fcm.cell_height - 1u));
|
||||
memset(buf + fcm.cell_width * top, 0xff, fcm.cell_width);
|
||||
memset(buf + fcm.cell_width * bottom, 0xff, fcm.cell_width);
|
||||
DecorationGeometry ans = {.top=top, .height = bottom + 1 - top};
|
||||
|
|
@ -72,7 +77,7 @@ add_double_underline(uint8_t *buf, FontCellMetrics fcm) {
|
|||
|
||||
static unsigned
|
||||
distribute_dots(unsigned available_space, unsigned num_of_dots, unsigned *summed_gaps, unsigned *gaps) {
|
||||
unsigned dot_size = MAX(1u, available_space / (2u * num_of_dots));
|
||||
unsigned dot_size = max(1u, available_space / (2u * num_of_dots));
|
||||
unsigned extra = 2 * num_of_dots * dot_size;
|
||||
extra = available_space > extra ? available_space - extra : 0;
|
||||
for (unsigned i = 0; i < num_of_dots; i++) gaps[i] = dot_size;
|
||||
|
|
@ -124,9 +129,9 @@ add_dashed_underline(uint8_t *buf, FontCellMetrics fcm) {
|
|||
static unsigned
|
||||
add_intensity(uint8_t *buf, unsigned x, unsigned y, uint8_t val, unsigned max_y, unsigned position, unsigned cell_width) {
|
||||
y += position;
|
||||
y = MIN(y, max_y);
|
||||
y = min(y, max_y);
|
||||
unsigned idx = cell_width * y + x;
|
||||
buf[idx] = MIN(255, buf[idx] + val);
|
||||
buf[idx] = min(255, buf[idx] + val);
|
||||
return y;
|
||||
}
|
||||
|
||||
|
|
@ -137,10 +142,10 @@ add_curl_underline(uint8_t *buf, FontCellMetrics fcm) {
|
|||
unsigned half_thickness = fcm.underline_thickness / 2;
|
||||
unsigned top = fcm.underline_position > half_thickness ? fcm.underline_position - half_thickness : 0;
|
||||
unsigned max_height = fcm.cell_height - top; // descender from the font
|
||||
unsigned half_height = MAX(1u, max_height / 4u);
|
||||
unsigned half_height = max(1u, max_height / 4u);
|
||||
unsigned thickness;
|
||||
if (OPT(undercurl_style) & 2) thickness = MAX(half_height, fcm.underline_thickness);
|
||||
else thickness = MAX(1u, fcm.underline_thickness) - (fcm.underline_thickness < 3u ? 1u : 2u);
|
||||
if (OPT(undercurl_style) & 2) thickness = max(half_height, fcm.underline_thickness);
|
||||
else thickness = max(1u, fcm.underline_thickness) - (fcm.underline_thickness < 3u ? 1u : 2u);
|
||||
unsigned position = fcm.underline_position;
|
||||
|
||||
// Ensure curve doesn't exceed cell boundary at the bottom
|
||||
|
|
@ -168,7 +173,7 @@ add_curl_underline(uint8_t *buf, FontCellMetrics fcm) {
|
|||
|
||||
static void
|
||||
vert(uint8_t *ans, bool is_left_edge, double width_pt, double dpi_x, FontCellMetrics fcm) {
|
||||
unsigned width = MAX(1u, MIN((unsigned)(round(width_pt * dpi_x / 72.0)), fcm.cell_width));
|
||||
unsigned width = max(1u, min((unsigned)(round(width_pt * dpi_x / 72.0)), fcm.cell_width));
|
||||
const unsigned left = is_left_edge ? 0 : (fcm.cell_width > width ? fcm.cell_width - width : 0);
|
||||
for (unsigned y = 0; y < fcm.cell_height; y++) {
|
||||
const unsigned offset = y * fcm.cell_width + left;
|
||||
|
|
@ -178,7 +183,7 @@ vert(uint8_t *ans, bool is_left_edge, double width_pt, double dpi_x, FontCellMet
|
|||
|
||||
static unsigned
|
||||
horz(uint8_t *ans, bool is_top_edge, double height_pt, double dpi_y, FontCellMetrics fcm) {
|
||||
unsigned height = MAX(1u, MIN((unsigned)(round(height_pt * dpi_y / 72.0)), fcm.cell_height));
|
||||
unsigned height = max(1u, min((unsigned)(round(height_pt * dpi_y / 72.0)), fcm.cell_height));
|
||||
const unsigned top = is_top_edge ? 0 : (fcm.cell_height > height ? fcm.cell_height - height : 0);
|
||||
for (unsigned y = top; y < top + height; y++) {
|
||||
const unsigned offset = y * fcm.cell_width;
|
||||
|
|
@ -210,3 +215,437 @@ add_hollow_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_x, double dpi_y)
|
|||
DecorationGeometry ans = {.height=fcm.cell_height};
|
||||
return ans;
|
||||
}
|
||||
typedef struct Range {
|
||||
uint start, end;
|
||||
} Range;
|
||||
|
||||
typedef struct Canvas {
|
||||
uint8_t *mask;
|
||||
uint width, height, supersample_factor;
|
||||
struct { double x, y; } dpi;
|
||||
Range *holes; uint holes_count, holes_capacity;
|
||||
struct { double upper, lower; } *y_limits; uint y_limits_count, y_limits_capacity;
|
||||
} Canvas;
|
||||
|
||||
static void
|
||||
append_hole(Canvas *self, Range hole) {
|
||||
ensure_space_for(self, holes, self->holes[0], self->holes_count + 1, holes_capacity, self->width, false);
|
||||
self->holes[self->holes_count++] = hole;
|
||||
}
|
||||
|
||||
static void
|
||||
append_limit(Canvas *self, double upper, double lower) {
|
||||
ensure_space_for(self, y_limits, self->y_limits[0], self->y_limits_count + 1, y_limits_capacity, self->width, false);
|
||||
self->y_limits[self->y_limits_count].upper = upper;
|
||||
self->y_limits[self->y_limits_count++].lower = lower;
|
||||
}
|
||||
|
||||
|
||||
static uint
|
||||
thickness(Canvas *self, uint level, bool horizontal) {
|
||||
level = min(level, arraysz(OPT(box_drawing_scale)));
|
||||
double pts = OPT(box_drawing_scale)[level];
|
||||
double dpi = horizontal ? self->dpi.x : self->dpi.y;
|
||||
return self->supersample_factor * (uint)ceil(pts * dpi / 72.0);
|
||||
}
|
||||
|
||||
static uint
|
||||
minus(uint a, uint b) { // saturating subtraction (a > b ? a - b : 0)
|
||||
uint res = a - b;
|
||||
res &= -(res <= a);
|
||||
return res;
|
||||
}
|
||||
|
||||
static const uint hole_factor = 8;
|
||||
|
||||
static void
|
||||
get_holes(Canvas *self, uint sz, uint hole_sz, uint num) {
|
||||
uint all_holes_use = (num + 1) * hole_sz;
|
||||
uint individual_block_size = max(1u, minus(sz, all_holes_use) / (num + 1));
|
||||
uint half_hole_sz = hole_sz / 2;
|
||||
int pos = - half_hole_sz;
|
||||
while (pos < (int)sz) {
|
||||
uint left = pos > 0 ? pos : 0;
|
||||
uint right = min(sz, pos + hole_sz);
|
||||
if (right > left) append_hole(self, (Range){left, right});
|
||||
pos = right + individual_block_size;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_hholes(Canvas *self, uint level, uint num) {
|
||||
uint line_sz = thickness(self, level, true);
|
||||
uint hole_sz = self->width / hole_factor;
|
||||
uint start = minus(self->height / 2, line_sz / 2);
|
||||
get_holes(self, self->width, hole_sz, num);
|
||||
for (uint y = 0; y < start + line_sz; y++) {
|
||||
uint offset = y * self->width;
|
||||
for (uint i = 0; i < self->holes_count; i++) memset(self->mask + offset + self->holes[i].start, 0, self->holes[i].end - self->holes[i].start);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
add_vholes(Canvas *self, uint level, uint num) {
|
||||
uint line_sz = thickness(self, level, false);
|
||||
uint hole_sz = self->height / hole_factor;
|
||||
uint start = minus(self->width / 2, line_sz / 2);
|
||||
get_holes(self, self->height, hole_sz, num);
|
||||
for (uint i = 0; i < self->holes_count; i++) {
|
||||
for (uint y = self->holes[i].start; y < self->holes[i].end; y++) {
|
||||
uint offset = y * self->width;
|
||||
memset(self->mask + offset + start, 0, line_sz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
draw_hline(Canvas *self, uint x1, uint x2, uint y, uint level) {
|
||||
// Draw a horizontal line between [x1, x2) centered at y with the thickness given by level and self->supersample_factor
|
||||
uint sz = thickness(self, level, false);
|
||||
uint start = minus(y, sz / 2);
|
||||
for (uint y = start; y < min(start + sz, self->height); y++) {
|
||||
uint8_t *py = self->mask + y * self->width;
|
||||
memset(py + x1, 255, minus(x2, x1));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
draw_vline(Canvas *self, uint y1, uint y2, uint x, uint level) {
|
||||
// Draw a vertical line between [y1, y2) centered at x with the thickness given by level and self->supersample_factor
|
||||
uint sz = thickness(self, level, true);
|
||||
uint start = minus(x, sz / 2), end = min(start + sz, self->width), xsz = minus(end, start);
|
||||
for (uint y = y1; y < y2; y++) {
|
||||
uint8_t *py = self->mask + y * self->width;
|
||||
memset(py + start, 255, xsz);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
half_hline(Canvas *self, uint level, bool right_half, uint extend_by) {
|
||||
uint x1, x2;
|
||||
if (right_half) {
|
||||
x1 = minus(self->width / 2u, extend_by); x2 = self->width;
|
||||
} else {
|
||||
x1 = 0; x2 = self->width / 2 + extend_by;
|
||||
}
|
||||
draw_hline(self, x1, x2, self->height / 2, level);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
half_vline(Canvas *self, uint level, bool bottom_half, uint extend_by) {
|
||||
uint y1, y2;
|
||||
if (bottom_half) {
|
||||
y1 = minus(self->height / 2u, extend_by); y2 = self->height;
|
||||
} else {
|
||||
y1 = 0; y2 = self->height / 2 + extend_by;
|
||||
}
|
||||
draw_vline(self, y1, y2, self->width / 2, level);
|
||||
}
|
||||
|
||||
static void
|
||||
hline(Canvas *self, uint level) {
|
||||
half_hline(self, level, false, 0);
|
||||
half_hline(self, level, true, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
vline(Canvas *self, uint level) {
|
||||
half_vline(self, level, false, 0);
|
||||
half_vline(self, level, true, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
hholes(Canvas *self, uint level, uint num) {
|
||||
hline(self, level);
|
||||
add_hholes(self, level, num);
|
||||
}
|
||||
|
||||
static void
|
||||
vholes(Canvas *self, uint level, uint num) {
|
||||
vline(self, level);
|
||||
add_vholes(self, level, num);
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
plus(uint8_t a, uint8_t b) {
|
||||
uint8_t res = a + b;
|
||||
res |= -(res < a);
|
||||
return res;
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
average_intensity(const Canvas *src, uint dest_x, uint dest_y) {
|
||||
uint src_x = dest_x * src->supersample_factor, src_y = dest_y * src->supersample_factor;
|
||||
uint total = 0;
|
||||
for (uint y = src_y; y < src_y + src->supersample_factor; y++) {
|
||||
uint offset = src->width * y;
|
||||
for (uint x = src_x; x < src_x + src->supersample_factor; x++) total += src->mask[offset + x];
|
||||
}
|
||||
return (total / (src->supersample_factor * src->supersample_factor)) & 0xff;
|
||||
}
|
||||
|
||||
static void
|
||||
downsample(const Canvas *src, Canvas *dest) {
|
||||
for (uint y = 0; y < dest->height; y++) {
|
||||
uint offset = dest->width * y;
|
||||
for (uint x = 0; x < dest->width; x++) {
|
||||
dest->mask[offset + x] = plus(dest->mask[offset + x], average_intensity(src, x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct StraightLine {
|
||||
double m, c;
|
||||
} StraightLine;
|
||||
|
||||
|
||||
static StraightLine
|
||||
line_from_points(int x1, int y1, int x2, int y2) {
|
||||
StraightLine ans = {.m = (y2 - y1) / ((double)(x2 - x1))};
|
||||
ans.c = y1 - ans.m * x1;
|
||||
return ans;
|
||||
}
|
||||
|
||||
static double
|
||||
line_y(StraightLine l, int x) {
|
||||
return l.m * x + l.c;
|
||||
}
|
||||
|
||||
#define calc_limits(self, lower_y, upper_y) { \
|
||||
if (!self->y_limits) { \
|
||||
self->y_limits_count = self->width; self->y_limits = malloc(sizeof(self->y_limits[0]) * self->y_limits_count); \
|
||||
if (!self->y_limits) fatal("Out of memory"); \
|
||||
} \
|
||||
for (uint x = 0; x < self->width; x++) { self->y_limits[x].lower = lower_y; self->y_limits[x].upper = upper_y; } \
|
||||
}
|
||||
|
||||
static void
|
||||
fill_region(Canvas *self, bool inverted) {
|
||||
uint8_t full = 0, empty = 0; if (inverted) empty = 255; else full = 255;
|
||||
for (uint y = 0; y < self->height; y++) {
|
||||
uint offset = y * self->width;
|
||||
for (uint x = 0; x < self->width && x < self->y_limits_count; x++) {
|
||||
self->mask[offset + x] = self->y_limits[x].lower <= y && y <= self->y_limits[x].upper ? full : empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
triangle(Canvas *self, bool left, bool inverted) {
|
||||
int ay1 = 0, by1 = self->height - 1, y2 = self->height / 2, x1 = 0, x2 = 0;
|
||||
if (left) x2 = self->width - 1; else x1 = self->width - 1;
|
||||
StraightLine uppery = line_from_points(x1, ay1, x2, y2);
|
||||
StraightLine lowery = line_from_points(x1, by1, x2, y2);
|
||||
calc_limits(self, line_y(uppery, x), line_y(lowery, x));
|
||||
fill_region(self, inverted);
|
||||
}
|
||||
|
||||
typedef enum Corner {
|
||||
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT
|
||||
} Corner;
|
||||
|
||||
typedef struct Point { int x, y; } Point;
|
||||
|
||||
static void
|
||||
thick_line(Canvas *self, uint thickness_in_pixels, Point p1, Point p2) {
|
||||
if (p1.x > p2.x) SWAP(p1, p2);
|
||||
StraightLine l = line_from_points(p1.x, p1.y, p2.x, p2.y);
|
||||
div_t d = div(thickness_in_pixels, 2);
|
||||
int delta = d.quot, extra = d.rem;
|
||||
for (int x = p1.x > 0 ? p1.x : 0; x < (int)self->width && x < p2.x + 1; x++) {
|
||||
int y_p = (int)line_y(l, x);
|
||||
for (int y = MAX(0, y_p - delta); y < MIN(y_p + delta + extra, (int)self->height); y++) {
|
||||
self->mask[x + y * self->width] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
half_cross_line(Canvas *self, uint level, Corner corner) {
|
||||
uint my = minus(self->height, 1) / 2; Point p1 = {0}, p2 = {0};
|
||||
switch (corner) {
|
||||
case TOP_LEFT: p2.x = minus(self->width, 1); p2.y = my; break;
|
||||
case BOTTOM_LEFT: p1.x = minus(self->width, 1); p1.y = my; p2.y = self->height -1; break;
|
||||
case TOP_RIGHT: p1.x = minus(self->width, 1); p2.y = my; break;
|
||||
case BOTTOM_RIGHT: p2.x = minus(self->width, 1), p2.y = minus(self->height, 1); p1.y = my; break;
|
||||
}
|
||||
thick_line(self, thickness(self, level, true), p1, p2);
|
||||
}
|
||||
|
||||
typedef struct CubicBezier {
|
||||
Point start, c1, c2, end;
|
||||
} CubicBezier;
|
||||
|
||||
#define bezier_eq(which) { \
|
||||
double tm1 = 1 - t; \
|
||||
double tm1_3 = tm1 * tm1 * tm1; \
|
||||
double t_3 = t * t * t; \
|
||||
return tm1_3 * cb.start.which + 3 * t * tm1 * (tm1 * cb.c1.which + t * cb.c2.which) + t_3 * cb.end.which; \
|
||||
}
|
||||
static double
|
||||
bezier_x(CubicBezier cb, double t) { bezier_eq(x); }
|
||||
static double
|
||||
bezier_y(CubicBezier cb, double t) { bezier_eq(y); }
|
||||
#undef bezier_eq
|
||||
|
||||
static int
|
||||
find_bezier_for_D(int width, int height) {
|
||||
int cx = width - 1, last_cx = cx;
|
||||
CubicBezier cb = {.end={0, height - 1}, .c2={0, height - 1}};
|
||||
while (true) {
|
||||
cb.c1.x = cx; cb.c2.x = cx;
|
||||
if (bezier_x(cb, 0.5) > width - 1) return last_cx;
|
||||
last_cx = cx++;
|
||||
}
|
||||
}
|
||||
|
||||
static double
|
||||
find_t_for_x(CubicBezier cb, int x, double start_t) {
|
||||
if (fabs(bezier_x(cb, start_t) - x) < 0.1) return start_t;
|
||||
static const double t_limit = 0.5;
|
||||
double increment = t_limit - start_t;
|
||||
if (increment <= 0) return start_t;
|
||||
while (true) {
|
||||
double q = bezier_x(cb, start_t + increment);
|
||||
if (fabs(q - x) < 0.1) return start_t + increment;
|
||||
if (q > x) {
|
||||
increment /= 2.0;
|
||||
if (increment < 1e-6) {
|
||||
log_error("Failed to find cubic bezier t for x=%d\n", x);
|
||||
return start_t;
|
||||
}
|
||||
} else {
|
||||
start_t += increment;
|
||||
increment = t_limit - start_t;
|
||||
if (increment <= 0) return start_t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
get_bezier_limits(Canvas *self, CubicBezier cb) {
|
||||
int start_x = (int)bezier_x(cb, 0), max_x = (int)bezier_x(cb, 0.5);
|
||||
double last_t = 0.;
|
||||
for (int x = start_x; x < max_x + 1; x++) {
|
||||
if (x > start_x) last_t = find_t_for_x(cb, x, last_t);
|
||||
double upper = bezier_y(cb, last_t), lower = bezier_y(cb, 1.0 - last_t);
|
||||
if (fabs(upper - lower) <= 2.0) break; // avoid pip on end of D
|
||||
append_limit(self, lower, upper);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
filled_D(Canvas *self, bool left) {
|
||||
int c1x = find_bezier_for_D(self->width, self->height);
|
||||
CubicBezier cb = {.end={0, self->height-1}, .c1 = {c1x, 0}, .c2 = {c1x, self->height - 1}};
|
||||
get_bezier_limits(self, cb);
|
||||
if (left) fill_region(self, false);
|
||||
else {
|
||||
RAII_ALLOC(uint8_t, mbuf, calloc(self->width, self->height));
|
||||
if (!mbuf) fatal("Out of memory");
|
||||
uint8_t *buf = self->mask;
|
||||
self->mask = mbuf;
|
||||
fill_region(self, false);
|
||||
self->mask = buf;
|
||||
for (uint y = 0; y < self->height; y++) {
|
||||
uint offset = y * self->width;
|
||||
for (uint src_x = 0; src_x < self->width; src_x++) {
|
||||
uint dest_x = self->width - 1 - src_x;
|
||||
buf[offset + dest_x] = mbuf[offset + src_x];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#define NAME position_set
|
||||
#define KEY_TY int64_t
|
||||
#include "kitty-verstable.h"
|
||||
|
||||
#define draw_parametrized_curve(self, level, xfunc, yfunc) { \
|
||||
div_t d = div(thickness(self, level, true)); \
|
||||
int delta = d.quot, extra = d.rem; \
|
||||
uint num_samples = self->height * 8; \
|
||||
position_set seen; vt_init(&seen); \
|
||||
for (uint i = 0; i < num_samples; i++) { \
|
||||
double t = i / (double)num_samples; \
|
||||
int32_t x_p = xfunc, y_p = yfunc; \
|
||||
int64_t key = (x_p << 32) | y_p; \
|
||||
position_set_itr q = vt_get(&seen, key); \
|
||||
if (!vt_is_end(q)) continue; \
|
||||
if (vt_is_end(vt_insert(&seen, key))) fatal("Out of memory"); \
|
||||
for (int y = MAX(0, y_p - delta); y < MIN(y_p + delta + extra, (int)self->height); y++) { \
|
||||
uint offset = y * self->width, start = MAX(0, x_p - delta); \
|
||||
memset(self->mask + offset + start, 255, minus((uint)MIN(x_p + delta + extra, self->width), start)); \
|
||||
} \
|
||||
} \
|
||||
vt_cleanup(&seen); \
|
||||
}
|
||||
|
||||
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;
|
||||
ss.mask = buf + width*height; ss.supersample_factor = SUPERSAMPLE_FACTOR; ss.width *= SUPERSAMPLE_FACTOR; ss.height *= SUPERSAMPLE_FACTOR;
|
||||
memset(canvas.mask, 0, width * height * sizeof(canvas.mask[0]));
|
||||
Canvas *c = &canvas;
|
||||
|
||||
#define CC(expr) expr; break
|
||||
#define SS(expr) memset(ss.mask, 0, ss.width * ss.height * sizeof(ss.mask[0])); c = &ss, expr; downsample(&ss, &canvas); break
|
||||
#define C(ch, func, ...) case ch: CC(func(c, __VA_ARGS__))
|
||||
#define S(ch, func, ...) case ch: SS(func(c, __VA_ARGS__))
|
||||
|
||||
switch(ch) {
|
||||
C(L'─', hline, 1);
|
||||
C(L'━', hline, 3);
|
||||
C(L'│', vline, 1);
|
||||
C(L'┃', vline, 3);
|
||||
|
||||
C(L'╌', hholes, 1, 1);
|
||||
C(L'╍', hholes, 3, 1);
|
||||
C(L'┄', hholes, 1, 2);
|
||||
C(L'┅', hholes, 3, 2);
|
||||
C(L'┈', hholes, 1, 3);
|
||||
C(L'┉', hholes, 3, 3);
|
||||
|
||||
C(L'╎', vholes, 1, 1);
|
||||
C(L'╏', vholes, 3, 1);
|
||||
C(L'┆', vholes, 1, 2);
|
||||
C(L'┇', vholes, 3, 2);
|
||||
C(L'┊', vholes, 1, 3);
|
||||
C(L'┋', vholes, 3, 3);
|
||||
|
||||
C(L'╴', half_hline, 1, false, 0);
|
||||
C(L'╵', half_vline, 1, false, 0);
|
||||
C(L'╶', half_hline, 1, true, 0);
|
||||
C(L'╷', half_vline, 1, true, 0);
|
||||
C(L'╸', half_hline, 3, false, 0);
|
||||
C(L'╹', half_vline, 3, false, 0);
|
||||
C(L'╺', half_hline, 3, true, 0);
|
||||
C(L'╻', half_vline, 3, true, 0);
|
||||
case L'╾': CC(half_hline(c, 3, false, 0); half_hline(c, 1, true, 0));
|
||||
case L'╼': CC(half_hline(c, 1, false, 0); half_hline(c, 3, true, 0));
|
||||
case L'╿': CC(half_vline(c, 3, false, 0); half_vline(c, 1, true, 0));
|
||||
case L'╽': CC(half_vline(c, 1, false, 0); half_vline(c, 3, true, 0));
|
||||
|
||||
S(L'', triangle, true, false);
|
||||
S(L'', triangle, true, true);
|
||||
case L'': SS(half_cross_line(c, 1, TOP_LEFT); half_cross_line(c, 1, BOTTOM_LEFT));
|
||||
S(L'', triangle, false, false);
|
||||
S(L'', triangle, false, true);
|
||||
case L'': SS(half_cross_line(c, 1, TOP_RIGHT); half_cross_line(c, 1, BOTTOM_RIGHT));
|
||||
|
||||
S(L'', filled_D, true);
|
||||
S(L'◗', filled_D, true);
|
||||
S(L'', filled_D, false);
|
||||
S(L'◖', filled_D, false);
|
||||
}
|
||||
#undef CC
|
||||
#undef SS
|
||||
#undef C
|
||||
#undef S
|
||||
free(canvas.holes); free(canvas.y_limits);
|
||||
free(ss.holes); free(ss.y_limits);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,3 +23,5 @@ DecorationGeometry add_missing_glyph(uint8_t *buf, FontCellMetrics fcm);
|
|||
DecorationGeometry add_beam_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_x);
|
||||
DecorationGeometry add_underline_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_y);
|
||||
DecorationGeometry add_hollow_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_x, double dpi_y);
|
||||
void render_box_char(char_type ch, uint8_t *buf, unsigned width, unsigned height, double dpi_x, double dpi_y);
|
||||
#define SUPERSAMPLE_FACTOR 4u
|
||||
|
|
|
|||
|
|
@ -998,7 +998,7 @@ def ring_bell() -> None:
|
|||
pass
|
||||
|
||||
|
||||
def concat_cells(cell_width: int, cell_height: int, is_32_bit: bool, cells: Tuple[bytes, ...]) -> bytes:
|
||||
def concat_cells(cell_width: int, cell_height: int, is_32_bit: bool, cells: Tuple[bytes, ...], bgcolor: int = 0) -> bytes:
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -1715,6 +1715,7 @@ def glfw_get_system_color_theme(query_if_unintialized: bool = True) -> Literal['
|
|||
def set_redirect_keys_to_overlay(os_window_id: int, tab_id: int, window_id: int, overlay_window_id: int) -> None: ...
|
||||
def buffer_keys_in_window(os_window_id: int, tab_id: int, window_id: int, enabled: bool = True) -> bool: ...
|
||||
def sprite_idx_to_pos(idx: int, xnum: int, ynum: int) -> tuple[int, int, int]: ...
|
||||
def render_box_char(ch: int, width: int, height: int, dpi_x: float = 96.0, dpi_y: float = 96.0) -> bytes: ...
|
||||
|
||||
class MousePosition(TypedDict):
|
||||
cell_x: int
|
||||
|
|
|
|||
|
|
@ -67,8 +67,9 @@ typedef struct {
|
|||
|
||||
typedef struct Canvas {
|
||||
pixel *buf;
|
||||
uint8_t *alpha_mask;
|
||||
unsigned current_cells, alloced_cells, alloced_scale, current_scale;
|
||||
size_t size_in_bytes;
|
||||
size_t size_in_bytes, alpha_mask_sz_in_bytes;
|
||||
} Canvas;
|
||||
|
||||
#define NAME fallback_font_map_t
|
||||
|
|
@ -175,6 +176,12 @@ ensure_canvas_can_fit(FontGroup *fg, unsigned cells, unsigned scale) {
|
|||
fg->canvas.current_scale = scale;
|
||||
if (fg->canvas.buf) memset(fg->canvas.buf, 0, cs(cells, scale));
|
||||
#undef cs
|
||||
size_in_bytes = (sizeof(fg->canvas.alpha_mask[0]) * SUPERSAMPLE_FACTOR * SUPERSAMPLE_FACTOR * 2 * fg->fcm.cell_width * fg->fcm.cell_height * scale * scale);
|
||||
if (size_in_bytes > fg->canvas.alpha_mask_sz_in_bytes) {
|
||||
fg->canvas.alpha_mask_sz_in_bytes = size_in_bytes;
|
||||
fg->canvas.alpha_mask = malloc(fg->canvas.alpha_mask_sz_in_bytes * sizeof(fg->canvas.alpha_mask[0]));
|
||||
if (!fg->canvas.alpha_mask) fatal("Out of memory allocating canvas");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -225,7 +232,7 @@ del_font(Font *f) {
|
|||
|
||||
static void
|
||||
del_font_group(FontGroup *fg) {
|
||||
free(fg->canvas.buf); fg->canvas.buf = NULL; fg->canvas = (Canvas){0};
|
||||
free(fg->canvas.buf); free(fg->canvas.alpha_mask); fg->canvas = (Canvas){0};
|
||||
free_sprite_data((FONTS_DATA_HANDLE)fg);
|
||||
vt_cleanup(&fg->fallback_font_map);
|
||||
vt_cleanup(&fg->scaled_font_map);
|
||||
|
|
@ -1034,18 +1041,16 @@ render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell,
|
|||
RAII_PyObject(ret, NULL); uint8_t *alpha_mask = NULL;
|
||||
ensure_canvas_can_fit(fg, num_glyphs + 1, rf.scale);
|
||||
if (num_glyphs == 1) {
|
||||
ret = PyObject_CallFunction(box_drawing_function, "IIId", ch, width, height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0);
|
||||
if (ret == NULL) failed;
|
||||
alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0));
|
||||
render_box_char(ch, fg->canvas.alpha_mask, width, height, fg->logical_dpi_x, fg->logical_dpi_y);
|
||||
alpha_mask = fg->canvas.alpha_mask;
|
||||
} else {
|
||||
alpha_mask = ((uint8_t*)fg->canvas.buf) + fg->canvas.size_in_bytes - (num_glyphs * width * height);
|
||||
unsigned cnum = 0;
|
||||
for (unsigned i = 0; i < num_glyphs; i++) {
|
||||
unsigned int ch = global_glyph_render_scratch.lc->chars[cnum++];
|
||||
while (!ch) ch = global_glyph_render_scratch.lc->chars[cnum++];
|
||||
RAII_PyObject(r, PyObject_CallFunction(box_drawing_function, "IIId", ch, width, height, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0));
|
||||
if (r == NULL) failed;
|
||||
uint8_t *src = PyLong_AsVoidPtr(PyTuple_GET_ITEM(r, 0));
|
||||
render_box_char(ch, fg->canvas.alpha_mask, width, height, fg->logical_dpi_x, fg->logical_dpi_y);
|
||||
uint8_t *src = fg->canvas.alpha_mask;
|
||||
for (unsigned y = 0; y < height; y++) {
|
||||
uint8_t *dest_row = alpha_mask + y*num_glyphs*width + i*width;
|
||||
memcpy(dest_row, src + y * width, width);
|
||||
|
|
@ -2076,13 +2081,27 @@ test_render_line(PyObject UNUSED *self, PyObject *args) {
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
alpha_blend(uint32_t fg, uint32_t bg) {
|
||||
uint32_t r1 = (fg >> 16) & 0xFF, g1 = (fg >> 8) & 0xFF, b1 = fg & 0xFF, a = (fg >> 24) & 0xff;
|
||||
uint32_t r2 = (bg >> 16) & 0xFF, g2 = (bg >> 8) & 0xFF, b2 = bg & 0xFF;
|
||||
float alpha = a / 255.f;
|
||||
|
||||
#define mix(x) uint32_t x = ((uint32_t)(alpha * x##1 + (1.0f - alpha) * x##2)) & 0xff;
|
||||
mix(r); mix(g); mix(b);
|
||||
#undef mix
|
||||
// Combine components into result color
|
||||
return (0xff000000) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
concat_cells(PyObject UNUSED *self, PyObject *args) {
|
||||
// Concatenate cells returning RGBA data
|
||||
unsigned int cell_width, cell_height;
|
||||
int is_32_bit;
|
||||
PyObject *cells;
|
||||
if (!PyArg_ParseTuple(args, "IIpO!", &cell_width, &cell_height, &is_32_bit, &PyTuple_Type, &cells)) return NULL;
|
||||
unsigned long bgcolor = 0;
|
||||
if (!PyArg_ParseTuple(args, "IIpO!|k", &cell_width, &cell_height, &is_32_bit, &PyTuple_Type, &cells, &bgcolor)) return NULL;
|
||||
size_t num_cells = PyTuple_GET_SIZE(cells), r, c, i;
|
||||
PyObject *ans = PyBytes_FromStringAndSize(NULL, (size_t)4 * cell_width * cell_height * num_cells);
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
|
|
@ -2092,22 +2111,11 @@ concat_cells(PyObject UNUSED *self, PyObject *args) {
|
|||
void *s = ((uint8_t*)PyBytes_AS_STRING(PyTuple_GET_ITEM(cells, c)));
|
||||
if (is_32_bit) {
|
||||
pixel *src = (pixel*)s + cell_width * r;
|
||||
for (i = 0; i < cell_width; i++, dest++) {
|
||||
uint8_t *rgba = (uint8_t*)dest;
|
||||
rgba[0] = (src[i] >> 24) & 0xff;
|
||||
rgba[1] = (src[i] >> 16) & 0xff;
|
||||
rgba[2] = (src[i] >> 8) & 0xff;
|
||||
rgba[3] = src[i] & 0xff;
|
||||
}
|
||||
for (i = 0; i < cell_width; i++, dest++) dest[0] = alpha_blend(src[0], bgcolor);
|
||||
} else {
|
||||
uint8_t *src = (uint8_t*)s + cell_width * r;
|
||||
for (i = 0; i < cell_width; i++, dest++) {
|
||||
uint8_t *rgba = (uint8_t*)dest;
|
||||
if (src[i]) { memset(rgba, 0xff, 3); rgba[3] = src[i]; }
|
||||
else *dest = 0;
|
||||
}
|
||||
for (i = 0; i < cell_width; i++, dest++) dest[0] = alpha_blend(0x00ffffff | ((src[i] & 0xff) << 24), bgcolor);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
|
|
@ -2293,6 +2301,18 @@ sprite_idx_to_pos(PyObject *self UNUSED, PyObject *args) {
|
|||
return Py_BuildValue("III", x, y, z);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
pyrender_box_char(PyObject *self UNUSED, PyObject *args) {
|
||||
unsigned int ch;
|
||||
unsigned long width, height; double dpi_x = 96., dpi_y = 96.;
|
||||
if (!PyArg_ParseTuple(args, "Ikk|dd", &ch, &width, &height, &dpi_x, &dpi_y)) return NULL;
|
||||
RAII_PyObject(ans, PyBytes_FromStringAndSize(NULL, width*16 * height*16));
|
||||
if (!ans) return NULL;
|
||||
render_box_char(ch, (uint8_t*)PyBytes_AS_STRING(ans), width, height, dpi_x, dpi_y);
|
||||
if (_PyBytes_Resize(&ans, width * height) != 0) return NULL;
|
||||
return Py_NewRef(ans);
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHODB(set_font_data, METH_VARARGS),
|
||||
METHODB(sprite_idx_to_pos, METH_VARARGS),
|
||||
|
|
@ -2308,6 +2328,7 @@ static PyMethodDef module_methods[] = {
|
|||
METHODB(test_render_line, METH_VARARGS),
|
||||
METHODB(get_fallback_font, METH_VARARGS),
|
||||
{"specialize_font_descriptor", (PyCFunction)pyspecialize_font_descriptor, METH_VARARGS, ""},
|
||||
{"render_box_char", (PyCFunction)pyrender_box_char, METH_VARARGS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@
|
|||
|
||||
import math
|
||||
from collections.abc import Iterable, Iterator, MutableSequence, Sequence
|
||||
from functools import lru_cache, wraps
|
||||
from functools import partial as p
|
||||
from functools import lru_cache, partial
|
||||
from itertools import repeat
|
||||
from typing import Any, Callable, Literal, Optional
|
||||
|
||||
|
|
@ -18,6 +17,13 @@ _dpi = 96.0
|
|||
BufType = MutableSequence[int]
|
||||
|
||||
|
||||
def p(f: Any, *a: Any, **kw: Any) -> Any:
|
||||
ans = partial(f, *a, **kw)
|
||||
if hasattr(f, 'supersample_factor'):
|
||||
setattr(ans, 'supersample_factor', f.supersample_factor)
|
||||
return ans
|
||||
|
||||
|
||||
def set_scale(new_scale: Sequence[float]) -> None:
|
||||
global scale
|
||||
scale = (new_scale[0], new_scale[1], new_scale[2], new_scale[3])
|
||||
|
|
@ -144,6 +150,11 @@ def cross(buf: BufType, width: int, height: int, a: int = 1, b: int = 1, c: int
|
|||
half_vline(buf, width, height, level=d, which='bottom')
|
||||
|
||||
|
||||
def print_hash(x: bytes, prefix: str = 'native:') -> None:
|
||||
from hashlib import sha256
|
||||
print(prefix, sha256(x).hexdigest())
|
||||
|
||||
|
||||
def downsample(src: BufType, dest: BufType, dest_width: int, dest_height: int, factor: int = 4) -> None:
|
||||
src_width = factor * dest_width
|
||||
|
||||
|
|
@ -167,19 +178,23 @@ 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')
|
||||
w, h = supersample_factor * width, supersample_factor * height
|
||||
ssbuf = SSByteArray(w * h)
|
||||
ssbuf.supersample_factor = supersample_factor
|
||||
for f in funcs:
|
||||
f(ssbuf, w, h)
|
||||
downsample(ssbuf, buf, width, height, factor=supersample_factor)
|
||||
|
||||
|
||||
def supersampled(supersample_factor: int = 4) -> Callable[[Callable[..., None]], Callable[..., None]]:
|
||||
# Anti-alias the drawing performed by the wrapped function by
|
||||
# using supersampling
|
||||
|
||||
def create_wrapper(f: Callable[..., None]) -> Callable[..., None]:
|
||||
@wraps(f)
|
||||
def supersampled_wrapper(buf: BufType, width: int, height: int, *args: Any, **kw: Any) -> None:
|
||||
w, h = supersample_factor * width, supersample_factor * height
|
||||
ssbuf = SSByteArray(w * h)
|
||||
ssbuf.supersample_factor = supersample_factor
|
||||
f(ssbuf, w, h, *args, **kw)
|
||||
downsample(ssbuf, buf, width, height, factor=supersample_factor)
|
||||
return supersampled_wrapper
|
||||
setattr(f, 'supersample_factor', supersample_factor)
|
||||
return f
|
||||
return create_wrapper
|
||||
|
||||
|
||||
|
|
@ -1429,32 +1444,84 @@ for i in range(1, 7):
|
|||
def render_box_char(ch: str, buf: BufType, width: int, height: int, dpi: float = 96.0) -> BufType:
|
||||
global _dpi
|
||||
_dpi = dpi
|
||||
for func in box_chars[ch]:
|
||||
func(buf, width, height)
|
||||
funcs = box_chars[ch]
|
||||
if hasattr(funcs[0], 'supersample_factor'):
|
||||
ss(buf, width, height, *funcs)
|
||||
else:
|
||||
for func in box_chars[ch]:
|
||||
func(buf, width, height)
|
||||
return buf
|
||||
|
||||
|
||||
def test_char(ch: str, sz: int = 48) -> None:
|
||||
# kitty +runpy "from kitty.fonts.box_drawing import test_char; test_char('XXX')"
|
||||
def test_chars(chars: str = '╌', sz: int = 128) -> None:
|
||||
# kitty +runpy "from kitty.fonts.box_drawing import test_chars; test_chars('XXX')"
|
||||
from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu
|
||||
from kitty.fast_data_types import render_box_char as native_render_box_char
|
||||
|
||||
from .render import display_bitmap, setup_for_testing
|
||||
if not chars:
|
||||
import sys
|
||||
chars = sys.argv[-1]
|
||||
with setup_for_testing('monospace', sz) as (_, width, height):
|
||||
buf = bytearray(width * height)
|
||||
try:
|
||||
render_box_char(ch, buf, width, height)
|
||||
for ch in chars:
|
||||
print('Rendering', ch)
|
||||
buf = bytearray(width * height)
|
||||
render_box_char(ch, buf, width, height)
|
||||
|
||||
def join_cells(*cells: bytes) -> bytes:
|
||||
cells = tuple(bytes(x) for x in cells)
|
||||
return concat_cells(width, height, False, cells)
|
||||
def join_cells(*cells: bytes) -> bytes:
|
||||
cells = tuple(bytes(x) for x in cells)
|
||||
return concat_cells(width, height, False, cells)
|
||||
|
||||
rgb_data = join_cells(buf)
|
||||
display_bitmap(rgb_data, width, height)
|
||||
print()
|
||||
rgb_data = join_cells(buf)
|
||||
display_bitmap(rgb_data, width, height)
|
||||
print()
|
||||
|
||||
nb = native_render_box_char(ord(ch), width, height)
|
||||
rgb_data = concat_cells(width, height, False, (nb,))
|
||||
display_bitmap(rgb_data, width, height)
|
||||
print()
|
||||
finally:
|
||||
set_send_sprite_to_gpu(None)
|
||||
|
||||
|
||||
def port_chars() -> None:
|
||||
from kitty.fast_data_types import concat_cells
|
||||
from kitty.fast_data_types import render_box_char as native_render_box_char
|
||||
|
||||
from .render import display_bitmap, setup_for_testing
|
||||
|
||||
def join_cells(*cells: bytes) -> bytes:
|
||||
cells = tuple(bytes(x) for x in cells)
|
||||
return concat_cells(width, height, False, cells)
|
||||
|
||||
for sz in (127, 8, 11, 12, 13):
|
||||
with setup_for_testing('monospace', sz) as (_, width, height):
|
||||
for ch in box_chars:
|
||||
buf = bytearray(width * height)
|
||||
render_box_char(ch, buf, width, height)
|
||||
nb = native_render_box_char(ord(ch), width, height)
|
||||
if bytes(buf) != nb:
|
||||
print(f'Failed to match for char: {ch=} ({hex(ord(ch))}) at {width=} {height=}')
|
||||
count = 0
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if buf[y*width + x] != nb[y*width + x]:
|
||||
print(f'differing byte at {x=} {y=}. Expected: {buf[y*width + x]} Actual: {nb[y*width + x]}')
|
||||
count += 1
|
||||
if count > 5:
|
||||
break
|
||||
if count > 5:
|
||||
break
|
||||
rgb_data = join_cells(buf)
|
||||
display_bitmap(rgb_data, width, height)
|
||||
print()
|
||||
rgb_data = concat_cells(width, height, False, (nb,))
|
||||
display_bitmap(rgb_data, width, height)
|
||||
print()
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def test_drawing(sz: int = 48, family: str = 'monospace', start: int = 0x2500, num_rows: int = 10, num_cols: int = 16) -> None:
|
||||
from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu
|
||||
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ might cause rendering artifacts, so use with care.
|
|||
''')
|
||||
|
||||
opt('box_drawing_scale', '0.001, 1, 1.5, 2',
|
||||
option_type='box_drawing_scale',
|
||||
option_type='box_drawing_scale', ctype='!box_drawing_scale',
|
||||
long_text='''
|
||||
The sizes of the lines used for the box drawing Unicode characters. These values
|
||||
are in pts. They will be scaled by the monitor DPI to arrive at a pixel value.
|
||||
|
|
|
|||
15
kitty/options/to-c-generated.h
generated
15
kitty/options/to-c-generated.h
generated
|
|
@ -70,6 +70,19 @@ convert_from_opts_modify_font(PyObject *py_opts, Options *opts) {
|
|||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_python_box_drawing_scale(PyObject *val, Options *opts) {
|
||||
box_drawing_scale(val, opts);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_opts_box_drawing_scale(PyObject *py_opts, Options *opts) {
|
||||
PyObject *ret = PyObject_GetAttrString(py_opts, "box_drawing_scale");
|
||||
if (ret == NULL) return;
|
||||
convert_from_python_box_drawing_scale(ret, opts);
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_python_undercurl_style(PyObject *val, Options *opts) {
|
||||
opts->undercurl_style = undercurl_style(val);
|
||||
|
|
@ -1174,6 +1187,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
|
|||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_modify_font(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_box_drawing_scale(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_undercurl_style(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_underline_exclusion(py_opts, opts);
|
||||
|
|
|
|||
|
|
@ -390,6 +390,13 @@ underline_exclusion(PyObject *val, Options *opts) {
|
|||
else opts->underline_exclusion.unit = 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
box_drawing_scale(PyObject *val, Options *opts) {
|
||||
for (unsigned i = 0; i < MIN(arraysz(opts->box_drawing_scale), (size_t)PyTuple_GET_SIZE(val)); i++) {
|
||||
opts->box_drawing_scale[i] = PyFloat_AsFloat(PyTuple_GET_ITEM(val, i));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
text_composition_strategy(PyObject *val, Options *opts) {
|
||||
if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "text_rendering_strategy must be a string"); return; }
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ typedef struct {
|
|||
struct { Animation *cursor, *visual_bell; } animation;
|
||||
unsigned undercurl_style;
|
||||
struct { float thickness; int unit; } underline_exclusion;
|
||||
float box_drawing_scale[4];
|
||||
} Options;
|
||||
|
||||
typedef struct WindowLogoRenderData {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue