From 6a169eedd50711a89fe83e76fe905da5c1bbb126 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 11 Dec 2024 19:47:52 +0530 Subject: [PATCH] Move decorations drawing code to C form Python Makes it faster and easier to call from C --- kitty/decorations.c | 209 ++++++++++++++++++++++++++++++++++++ kitty/decorations.h | 25 +++++ kitty/fast_data_types.pyi | 6 +- kitty/fonts.c | 61 +++++++---- kitty/fonts.h | 2 +- kitty/fonts/box_drawing.py | 4 - kitty/fonts/render.py | 213 +------------------------------------ 7 files changed, 276 insertions(+), 244 deletions(-) create mode 100644 kitty/decorations.c create mode 100644 kitty/decorations.h diff --git a/kitty/decorations.c b/kitty/decorations.c new file mode 100644 index 000000000..c8f11c89e --- /dev/null +++ b/kitty/decorations.c @@ -0,0 +1,209 @@ +/* + * decorations.c + * Copyright (C) 2024 Kovid Goyal + * + * 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; +} diff --git a/kitty/decorations.h b/kitty/decorations.h new file mode 100644 index 000000000..80ffb06f1 --- /dev/null +++ b/kitty/decorations.h @@ -0,0 +1,25 @@ +/* + * decorations.h + * Copyright (C) 2024 Kovid Goyal + * + * 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); diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index ddd59d1bd..da3db5ff3 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -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, diff --git a/kitty/fonts.c b/kitty/fonts.c index ff90a4620..aa1e3c414 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -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); diff --git a/kitty/fonts.h b/kitty/fonts.h index 3b70dfb5d..36a6933ba 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -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*); diff --git a/kitty/fonts/box_drawing.py b/kitty/fonts/box_drawing.py index 417af30da..4c435bad3 100644 --- a/kitty/fonts/box_drawing.py +++ b/kitty/fonts/box_drawing.py @@ -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 diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index a53ed7dbf..213115ca9 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -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()