Move decorations drawing code to C form Python

Makes it faster and easier to call from C
This commit is contained in:
Kovid Goyal 2024-12-11 19:47:52 +05:30
parent 9f84c32808
commit 6a169eedd5
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
7 changed files with 276 additions and 244 deletions

209
kitty/decorations.c Normal file
View file

@ -0,0 +1,209 @@
/*
* decorations.c
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "decorations.h"
#include "state.h"
#define STRAIGHT_UNDERLINE_LOOP \
unsigned half = fcm.underline_thickness / 2; \
DecorationGeometry ans = {.top = half > fcm.underline_position ? 0 : fcm.underline_position - half}; \
for (unsigned y = ans.top; fcm.underline_thickness > 0 && y < fcm.cell_height; fcm.underline_thickness--, y++, ans.height++)
DecorationGeometry
add_straight_underline(uint8_t *buf, FontCellMetrics fcm) {
STRAIGHT_UNDERLINE_LOOP {
memset(buf + fcm.cell_width * y, 0xff, fcm.cell_width * sizeof(buf[0]));
}
return ans;
}
DecorationGeometry
add_strikethrough(uint8_t *buf, FontCellMetrics fcm) {
unsigned half = fcm.strikethrough_thickness / 2;
DecorationGeometry ans = {.top = half > fcm.strikethrough_position ? 0 : fcm.strikethrough_position - half};
for (unsigned y = ans.top; fcm.strikethrough_thickness > 0 && y < fcm.cell_height; fcm.strikethrough_thickness--, y++, ans.height++) {
memset(buf + fcm.cell_width * y, 0xff, fcm.cell_width * sizeof(buf[0]));
}
return ans;
}
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);
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);
else {
memset(line, 0xff, thickness);
memset(line + fcm.cell_width - thickness, 0xff, thickness);
}
}
return ans;
}
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);
int deficit = 2 - (bottom - top);
if (deficit > 0) {
if (bottom + deficit < fcm.cell_height) bottom += deficit;
else if (bottom < fcm.cell_height - 1) {
bottom += 1;
if (deficit > 1) top -= deficit - 1;
} else top -= deficit;
}
top = MAX(0, MIN(top, fcm.cell_height - 1));
bottom = MAX(0, MIN(bottom, fcm.cell_height - 1));
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};
return ans;
}
static unsigned
distribute_dots(unsigned available_space, unsigned num_of_dots, unsigned *summed_gaps, unsigned *gaps) {
unsigned dot_size = MAX(1, available_space / (2 * 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;
if (extra > 0) {
unsigned idx = 0;
while (extra > 0) {
gaps[idx] += 1;
idx = (idx + 1) % num_of_dots;
extra--;
}
}
gaps[0] /= 2;
for (unsigned i = 0; i < num_of_dots; i++) {
summed_gaps[i] = 0;
for (unsigned g = 0; g <= i; g++) summed_gaps[i] += gaps[g];
}
return dot_size;
}
DecorationGeometry
add_dotted_underline(uint8_t *buf, FontCellMetrics fcm) {
unsigned num_of_dots = fcm.cell_width / (2 * fcm.underline_thickness);
RAII_ALLOC(unsigned, spacing, malloc(num_of_dots * 2 * sizeof(unsigned)));
if (!spacing) fatal("Out of memory");
unsigned size = distribute_dots(fcm.cell_width, num_of_dots, spacing, spacing + num_of_dots);
STRAIGHT_UNDERLINE_LOOP {
uint8_t *offset = buf + fcm.cell_width * y;
for (unsigned j = 0; j < num_of_dots; j++) {
unsigned s = spacing[j];
memset(offset + j * size + s, 0xff, size);
}
}
return ans;
}
DecorationGeometry
add_dashed_underline(uint8_t *buf, FontCellMetrics fcm) {
unsigned quarter_width = fcm.cell_width / 4;
unsigned dash_width = fcm.cell_width - 3 * quarter_width;
unsigned second_dash_start = 3 * quarter_width;
STRAIGHT_UNDERLINE_LOOP {
uint8_t *offset = buf + fcm.cell_width * y;
memset(offset, 0xff, dash_width);
memset(offset + second_dash_start, 0xff, dash_width);
}
return ans;
}
static void
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);
unsigned idx = cell_width * y + x;
buf[idx] = MIN(255, buf[idx] + val);
}
DecorationGeometry
add_curl_underline(uint8_t *buf, FontCellMetrics fcm) {
unsigned max_x = fcm.cell_width - 1, max_y = fcm.cell_height - 1;
double xfactor = ((OPT(undercurl_style) & 1) ? 4.0 : 2.0) * M_PI / max_x;
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(1, max_height / 4);
unsigned thickness;
if (OPT(undercurl_style) & 2) thickness = MAX(half_height, fcm.underline_thickness);
else thickness = MAX(1, fcm.underline_thickness) - (fcm.underline_thickness < 3 ? 1 : 2);
unsigned position = fcm.underline_position;
// Ensure curve doesn't exceed cell boundary at the bottom
position += half_height * 2;
if (position + half_height > max_y) position = max_y - half_height;
unsigned miny = fcm.cell_height, maxy = 0;
// Use the Wu antialias algorithm to draw the curve
// cosine waves always have slope <= 1 so are never steep
for (unsigned x = 0; x < fcm.cell_width; x++) {
double y = half_height * cos(x * xfactor);
unsigned y1 = (unsigned)floor(y - thickness), y2 = (unsigned)ceil(y);
unsigned i1 = (unsigned)(255. * fabs(y - floor(y)));
miny = MIN(miny, y1); maxy = MAX(maxy, y2);
add_intensity(buf, x, y1, 255 - i1, max_y, position, fcm.cell_width); // upper bound
add_intensity(buf, x, y2, i1, max_y, position, fcm.cell_width); // lower bound
// fill between upper and lower bound
for (unsigned t = 1; t <= thickness; t++) add_intensity(buf, x, y1 + t, 255, max_y, position, fcm.cell_width);
}
DecorationGeometry ans = {.top=miny, .height=maxy-miny + 1};
return ans;
}
static void
vert(uint8_t *ans, bool is_left_edge, double width_pt, double dpi_x, FontCellMetrics fcm) {
unsigned width = MAX(1, MIN((unsigned)(round(width_pt * dpi_x / 72.0)), fcm.cell_width));
const unsigned left = is_left_edge ? 0 : MAX(0, fcm.cell_width - width);
for (unsigned y = 0; y < fcm.cell_height; y++) {
const unsigned offset = y * fcm.cell_width + left;
for (unsigned x = offset; x < offset + width; x++) ans[x] = 0xff;
}
}
static unsigned
horz(uint8_t *ans, bool is_top_edge, double height_pt, double dpi_y, FontCellMetrics fcm) {
unsigned height = MAX(1, MIN((unsigned)(round(height_pt * dpi_y / 72.0)), fcm.cell_height));
const unsigned top = is_top_edge ? 0 : MAX(0, fcm.cell_height - height);
for (unsigned y = top; y < top + height; y++) {
const unsigned offset = y * fcm.cell_width;
for (unsigned x = 0; x < fcm.cell_width; x++) ans[offset + x] = 0xff;
}
return top;
}
DecorationGeometry
add_beam_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_x) {
vert(buf, true, OPT(cursor_beam_thickness), dpi_x, fcm);
DecorationGeometry ans = {.height=fcm.cell_height};
return ans;
}
DecorationGeometry
add_underline_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_y) {
DecorationGeometry ans = {0};
ans.top = horz(buf, true, OPT(cursor_underline_thickness), dpi_y, fcm);
ans.height = fcm.cell_height - ans.top;
return ans;
}
DecorationGeometry
add_hollow_cursor(uint8_t *buf, FontCellMetrics fcm, double dpi_x, double dpi_y) {
vert(buf, true, 1.0, dpi_x, fcm); vert(buf, false, 1.0, dpi_x, fcm);
horz(buf, true, 1.0, dpi_y, fcm); horz(buf, false, 1.0, dpi_y, fcm);
DecorationGeometry ans = {.height=fcm.cell_height};
return ans;
}

25
kitty/decorations.h Normal file
View file

@ -0,0 +1,25 @@
/*
* decorations.h
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include "data-types.h"
typedef struct DecorationGeometry {
uint32_t top, height;
} DecorationGeometry;
DecorationGeometry add_straight_underline(uint8_t *buf, FontCellMetrics fcm);
DecorationGeometry add_double_underline(uint8_t *buf, FontCellMetrics fcm);
DecorationGeometry add_dotted_underline(uint8_t *buf, FontCellMetrics fcm);
DecorationGeometry add_dashed_underline(uint8_t *buf, FontCellMetrics fcm);
DecorationGeometry add_curl_underline(uint8_t *buf, FontCellMetrics fcm);
DecorationGeometry add_strikethrough(uint8_t *buf, FontCellMetrics fcm);
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);

View file

@ -1132,11 +1132,7 @@ def set_send_sprite_to_gpu(
def set_font_data(
box_drawing_func: Callable[[int, int, int, float],
Tuple[int, Union[bytearray, bytes, Array[c_ubyte]]]],
prerender_func: Callable[
[int, int, int, int, int, int, int, float, float, float, float],
Tuple[Tuple[int, ...], Tuple[Array[c_ubyte], ...]]],
box_drawing_func: Callable[[int, int, int, float], Tuple[int, Union[bytearray, bytes, Array[c_ubyte]]]],
descriptor_for_idx: Callable[[int], Tuple[Union[FontObject|str], bool, bool]],
bold: int, italic: int, bold_italic: int, num_symbol_fonts: int,
symbol_maps: Tuple[Tuple[int, int, int], ...], font_sz_in_pts: float,

View file

@ -12,6 +12,7 @@
#include "state.h"
#include "emoji.h"
#include "unicode-data.h"
#include "decorations.h"
#include "glyph-cache.h"
#define MISSING_GLYPH (NUM_UNDERLINE_STYLES + 2)
@ -697,10 +698,10 @@ START_ALLOW_CASE_RANGE
END_ALLOW_CASE_RANGE
}
static PyObject* box_drawing_function = NULL, *prerender_function = NULL, *descriptor_for_idx = NULL;
static PyObject* box_drawing_function = NULL, *descriptor_for_idx = NULL;
void
render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride, pixel color_rgb) {
render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, const Region *src_rect, const Region *dest_rect, size_t src_stride, size_t dest_stride, pixel color_rgb) {
pixel col = color_rgb << 8;
for (size_t sr = src_rect->top, dr = dest_rect->top; sr < src_rect->bottom && dr < dest_rect->bottom; sr++, dr++) {
pixel *d = dest + dest_stride * dr;
@ -1744,12 +1745,12 @@ set_symbol_maps(SymbolMap **maps, size_t *num, const PyObject *sm) {
static PyObject*
set_font_data(PyObject UNUSED *m, PyObject *args) {
PyObject *sm, *ns;
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); Py_CLEAR(descriptor_for_idx);
if (!PyArg_ParseTuple(args, "OOOIIIIO!dO!",
&box_drawing_function, &prerender_function, &descriptor_for_idx,
Py_CLEAR(box_drawing_function); Py_CLEAR(descriptor_for_idx);
if (!PyArg_ParseTuple(args, "OOIIIIO!dO!",
&box_drawing_function, &descriptor_for_idx,
&descriptor_indices.bold, &descriptor_indices.italic, &descriptor_indices.bi, &descriptor_indices.num_symbol_fonts,
&PyTuple_Type, &sm, &OPT(font_size), &PyTuple_Type, &ns)) return NULL;
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); Py_INCREF(descriptor_for_idx);
Py_INCREF(box_drawing_function); Py_INCREF(descriptor_for_idx);
free_font_groups();
clear_symbol_maps();
set_symbol_maps(&symbol_maps, &num_symbol_maps, sm);
@ -1763,22 +1764,37 @@ send_prerendered_sprites(FontGroup *fg) {
// blank cell
ensure_canvas_can_fit(fg, 1, 1);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, 0, fg->canvas.buf);
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("Failed"); }
PyObject *args = PyObject_CallFunction(prerender_function, "IIIIIIIffdd", fg->fcm.cell_width, fg->fcm.cell_height, fg->fcm.baseline, fg->fcm.underline_position, fg->fcm.underline_thickness, fg->fcm.strikethrough_position, fg->fcm.strikethrough_thickness, OPT(cursor_beam_thickness), OPT(cursor_underline_thickness), fg->logical_dpi_x, fg->logical_dpi_y);
if (args == NULL) { PyErr_Print(); fatal("Failed to pre-render cells"); }
PyObject *cell_addresses = PyTuple_GET_ITEM(args, 0);
for (ssize_t i = 0; i < PyTuple_GET_SIZE(cell_addresses); i++) {
do_increment(fg, &error);
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("Failed"); }
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(cell_addresses, i));
ensure_canvas_can_fit(fg, 1, 1); // clear canvas
Region r = { .right = fg->fcm.cell_width, .bottom = fg->fcm.cell_height };
render_alpha_mask(alpha_mask, fg->canvas.buf, &r, &r, fg->fcm.cell_width, fg->fcm.cell_width, 0xffffff);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, current_sprite_index(&fg->sprite_tracker), fg->canvas.buf);
}
do_increment(fg, &error);
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("Failed"); }
Py_CLEAR(args);
const unsigned cell_area = fg->fcm.cell_height * fg->fcm.cell_width;
RAII_ALLOC(uint8_t, alpha_mask, malloc(cell_area));
if (!alpha_mask) fatal("Out of memory");
Region r = { .right = fg->fcm.cell_width, .bottom = fg->fcm.cell_height };
#define increment_sprite_index do_increment(fg, &error); if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("Failed"); }
#define do_one(call) \
memset(alpha_mask, 0, cell_area); \
call; \
ensure_canvas_can_fit(fg, 1, 1); /* clear canvas */ \
render_alpha_mask(alpha_mask, fg->canvas.buf, &r, &r, fg->fcm.cell_width, fg->fcm.cell_width, 0xffffff); \
increment_sprite_index; \
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, current_sprite_index(&fg->sprite_tracker), fg->canvas.buf);
// If you change the mapping of these cells you will need to change
// NUM_UNDERLINE_STYLES and BEAM_IDX in shader.c and STRIKE_SPRITE_INDEX in
// window.py and MISSING_GLYPH in font.c
do_one(add_straight_underline(alpha_mask, fg->fcm));
do_one(add_double_underline(alpha_mask, fg->fcm));
do_one(add_curl_underline(alpha_mask, fg->fcm));
do_one(add_dotted_underline(alpha_mask, fg->fcm));
do_one(add_dashed_underline(alpha_mask, fg->fcm));
do_one(add_strikethrough(alpha_mask, fg->fcm));
do_one(add_missing_glyph(alpha_mask, fg->fcm));
do_one(add_beam_cursor(alpha_mask, fg->fcm, fg->logical_dpi_x));
do_one(add_underline_cursor(alpha_mask, fg->fcm, fg->logical_dpi_y));
do_one(add_hollow_cursor(alpha_mask, fg->fcm, fg->logical_dpi_x, fg->logical_dpi_y));
increment_sprite_index;
#undef increment_sprite_index
#undef do_one
}
static size_t
@ -1854,7 +1870,6 @@ finalize(void) {
Py_CLEAR(python_send_to_gpu_impl);
clear_symbol_maps();
Py_CLEAR(box_drawing_function);
Py_CLEAR(prerender_function);
Py_CLEAR(descriptor_for_idx);
free_font_groups();
free(ligature_types);

View file

@ -50,7 +50,7 @@ bool face_equals_descriptor(PyObject *face_, PyObject *descriptor);
const char* postscript_name_for_face(const PyObject*);
void sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned int *y, unsigned int *z);
void render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride, pixel color_rgb);
void render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, const Region *src_rect, const Region *dest_rect, size_t src_stride, size_t dest_stride, pixel color_rgb);
void render_line(FONTS_DATA_HANDLE, Line *line, index_type lnum, Cursor *cursor, DisableLigature, ListOfChars*);
void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len);
typedef void (*free_extra_data_func)(void*);

View file

@ -1434,10 +1434,6 @@ def render_box_char(ch: str, buf: BufType, width: int, height: int, dpi: float =
return buf
def render_missing_glyph(buf: BufType, width: int, height: int) -> None:
frame(buf, width, height)
def test_char(ch: str, sz: int = 48) -> None:
# kitty +runpy "from kitty.fonts.box_drawing import test_char; test_char('XXX')"
from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu

View file

@ -5,18 +5,14 @@ import ctypes
import os
import sys
from collections.abc import Generator
from functools import partial
from math import ceil, cos, floor, pi
from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union, cast
from kitty.constants import fonts_dir, is_macos
from kitty.fast_data_types import (
NUM_UNDERLINE_STYLES,
Screen,
create_test_font_group,
current_fonts,
get_fallback_font,
get_options,
set_builtin_nerd_font,
set_font_data,
set_options,
@ -26,7 +22,7 @@ from kitty.fast_data_types import (
test_render_line,
test_shape,
)
from kitty.fonts.box_drawing import BufType, distribute_dots, render_box_char, render_missing_glyph
from kitty.fonts.box_drawing import BufType, render_box_char
from kitty.options.types import Options, defaults
from kitty.options.utils import parse_font_spec
from kitty.types import _T
@ -209,7 +205,7 @@ def set_font_family(opts: Optional[Options] = None, override_font_size: Optional
ns = create_narrow_symbols(opts)
num_symbol_fonts = len(current_faces) - before
set_font_data(
render_box_drawing, prerender_function, descriptor_for_idx,
render_box_drawing, descriptor_for_idx,
indices['bold'], indices['italic'], indices['bi'], num_symbol_fonts,
sm, sz, ns
)
@ -222,211 +218,6 @@ else:
UnderlineCallback = Callable[[CBufType, int, int, int, int], None]
def add_line(buf: CBufType, cell_width: int, position: int, thickness: int, cell_height: int) -> None:
y = position - thickness // 2
while thickness > 0 and -1 < y < cell_height:
thickness -= 1
ctypes.memset(ctypes.addressof(buf) + (cell_width * y), 255, cell_width)
y += 1
def add_dline(buf: CBufType, cell_width: int, position: int, thickness: int, cell_height: int) -> None:
a = min(position - thickness, cell_height - 1)
b = min(position, cell_height - 1)
top, bottom = min(a, b), max(a, b)
deficit = 2 - (bottom - top)
if deficit > 0:
if bottom + deficit < cell_height:
bottom += deficit
elif bottom < cell_height - 1:
bottom += 1
if deficit > 1:
top -= deficit - 1
else:
top -= deficit
top = max(0, min(top, cell_height - 1))
bottom = max(0, min(bottom, cell_height - 1))
for y in {top, bottom}:
ctypes.memset(ctypes.addressof(buf) + (cell_width * y), 255, cell_width)
def add_curl(buf: CBufType, cell_width: int, position: int, thickness: int, cell_height: int) -> None:
max_x, max_y = cell_width - 1, cell_height - 1
opts = get_options()
xfactor = (4.0 if 'dense' in opts.undercurl_style else 2.0) * pi / max_x
max_height = cell_height - (position - thickness // 2) # descender from the font
half_height = max(1, max_height // 4)
if 'thick' in opts.undercurl_style:
thickness = max(half_height, thickness)
else:
thickness = max(1, thickness) - (1 if thickness < 3 else 2)
def add_intensity(x: int, y: int, val: int) -> None:
y += position
y = min(y, max_y)
idx = cell_width * y + x
buf[idx] = min(255, buf[idx] + val)
# Ensure curve doesn't exceed cell boundary at the bottom
position += half_height * 2
if position + half_height > max_y:
position = max_y - half_height
# Use the Wu antialias algorithm to draw the curve
# cosine waves always have slope <= 1 so are never steep
for x in range(cell_width):
y = half_height * cos(x * xfactor)
y1, y2 = floor(y - thickness), ceil(y)
i1 = int(255 * abs(y - floor(y)))
add_intensity(x, y1, 255 - i1) # upper bound
add_intensity(x, y2, i1) # lower bound
# fill between upper and lower bound
for t in range(1, thickness + 1):
add_intensity(x, y1 + t, 255)
def add_dots(buf: CBufType, cell_width: int, position: int, thickness: int, cell_height: int) -> None:
spacing, size = distribute_dots(cell_width, cell_width // (2 * thickness))
y = position - thickness // 2
buf_addr = ctypes.addressof(buf)
while thickness > 0 and -1 < y < cell_height:
offset = buf_addr + cell_width * y
for j, s in enumerate(spacing):
ctypes.memset(offset + j * size + s, 255, size)
thickness -= 1
y += 1
def add_dashes(buf: CBufType, cell_width: int, position: int, thickness: int, cell_height: int) -> None:
halfspace_width = cell_width // 4
y = position - thickness // 2
dash_width = cell_width - 3 * halfspace_width
second_dash_start = 3 * halfspace_width
buf_addr = ctypes.addressof(buf)
while thickness > 0 and -1 < y < cell_height:
offset = buf_addr + cell_width * y
ctypes.memset(offset, 255, dash_width)
ctypes.memset(offset + second_dash_start, 255, dash_width)
thickness -= 1
y += 1
def render_special(
underline: int = 0,
strikethrough: bool = False,
missing: bool = False,
cell_width: int = 0, cell_height: int = 0,
baseline: int = 0,
underline_position: int = 0,
underline_thickness: int = 0,
strikethrough_position: int = 0,
strikethrough_thickness: int = 0,
dpi_x: float = 96.,
dpi_y: float = 96.,
) -> CBufType:
underline_position = min(underline_position, cell_height - sum(divmod(underline_thickness, 2)))
CharTexture = ctypes.c_ubyte * (cell_width * cell_height)
if missing:
buf = bytearray(cell_width * cell_height)
render_missing_glyph(buf, cell_width, cell_height)
return CharTexture.from_buffer(buf)
ans = CharTexture()
def dl(f: UnderlineCallback, *a: Any) -> None:
try:
f(ans, cell_width, *a)
except Exception as e:
log_error(f'Failed to render {f.__name__} at cell_width={cell_width} and cell_height={cell_height} with error: {e}')
if underline:
t = underline_thickness
if underline > 1:
t = max(1, min(cell_height - underline_position - 1, t))
dl([add_line, add_line, add_dline, add_curl, add_dots, add_dashes][underline], underline_position, t, cell_height)
if strikethrough:
dl(add_line, strikethrough_position, strikethrough_thickness, cell_height)
return ans
def render_cursor(
which: int,
cursor_beam_thickness: float,
cursor_underline_thickness: float,
cell_width: int = 0,
cell_height: int = 0,
dpi_x: float = 0,
dpi_y: float = 0
) -> CBufType:
CharTexture = ctypes.c_ubyte * (cell_width * cell_height)
ans = CharTexture()
def vert(edge: str, width_pt: float = 1) -> None:
width = max(1, min(int(round(width_pt * dpi_x / 72.0)), cell_width))
left = 0 if edge == 'left' else max(0, cell_width - width)
for y in range(cell_height):
offset = y * cell_width + left
for x in range(offset, offset + width):
ans[x] = 255
def horz(edge: str, height_pt: float = 1) -> None:
height = max(1, min(int(round(height_pt * dpi_y / 72.0)), cell_height))
top = 0 if edge == 'top' else max(0, cell_height - height)
for y in range(top, top + height):
offset = y * cell_width
for x in range(cell_width):
ans[offset + x] = 255
if which == 1: # beam
vert('left', cursor_beam_thickness)
elif which == 2: # underline
horz('bottom', cursor_underline_thickness)
elif which == 3: # hollow
vert('left')
vert('right')
horz('top')
horz('bottom')
return ans
def prerender_function(
cell_width: int,
cell_height: int,
baseline: int,
underline_position: int,
underline_thickness: int,
strikethrough_position: int,
strikethrough_thickness: int,
cursor_beam_thickness: float,
cursor_underline_thickness: float,
dpi_x: float,
dpi_y: float
) -> tuple[tuple[int, ...], tuple[CBufType, ...]]:
# Pre-render the special underline, strikethrough and missing and cursor cells
f = partial(
render_special, cell_width=cell_width, cell_height=cell_height, baseline=baseline,
underline_position=underline_position, underline_thickness=underline_thickness,
strikethrough_position=strikethrough_position, strikethrough_thickness=strikethrough_thickness,
dpi_x=dpi_x, dpi_y=dpi_y
)
c = partial(
render_cursor, cursor_beam_thickness=cursor_beam_thickness,
cursor_underline_thickness=cursor_underline_thickness, cell_width=cell_width,
cell_height=cell_height, dpi_x=dpi_x, dpi_y=dpi_y)
# If you change the mapping of these cells you will need to change
# NUM_UNDERLINE_STYLES and BEAM_IDX in shader.c and STRIKE_SPRITE_INDEX in
# window.py and MISSING_GLYPH in font.c
cells = list(map(f, range(1, NUM_UNDERLINE_STYLES + 1))) # underline sprites
cells.append(f(0, strikethrough=True)) # strikethrough sprite
cells.append(f(missing=True)) # missing glyph
cells.extend((c(1), c(2), c(3))) # cursor glyphs
tcells = tuple(cells)
return tuple(map(ctypes.addressof, tcells)), tcells
def render_box_drawing(codepoint: int, cell_width: int, cell_height: int, dpi: float) -> tuple[int, CBufType]:
CharTexture = ctypes.c_ubyte * (cell_width * cell_height)
buf = CharTexture()