mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-23 08:47:47 +02:00
Implement rendering of sample text
This commit is contained in:
@@ -678,6 +678,54 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
render_sample_text(CTFace *self, PyObject *args) {
|
||||
unsigned long canvas_width, canvas_height;
|
||||
unsigned long fg = 0xffffff;
|
||||
CTFontRef font = self->ct_font;
|
||||
PyObject *ptext;
|
||||
if (!PyArg_ParseTuple(args, "Ukk|k", &ptext, &canvas_width, &canvas_height, &fg)) return NULL;
|
||||
RAII_PyObject(pbuf, PyBytes_FromStringAndSize(NULL, sizeof(pixel) * canvas_width * canvas_height));
|
||||
if (!pbuf) return NULL;
|
||||
unsigned int cell_width, cell_height, baseline, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness;
|
||||
cell_metrics((PyObject*)self, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness, &strikethrough_position, &strikethrough_thickness);
|
||||
size_t num_chars = PyUnicode_GET_LENGTH(ptext);
|
||||
RAII_ALLOC(unichar, chars, calloc(sizeof(unichar), num_chars));
|
||||
if (!chars) return PyErr_NoMemory();
|
||||
for (size_t i = 0; i < num_chars; i++) chars[i] = PyUnicode_READ_CHAR(ptext, i);
|
||||
RAII_ALLOC(CGSize, local_advances, calloc(sizeof(CGSize), num_chars));
|
||||
if (!local_advances) return PyErr_NoMemory();
|
||||
ensure_render_space(0, 0, num_chars);
|
||||
CTFontGetGlyphsForCharacters(font, chars, buffers.glyphs, num_chars);
|
||||
CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, buffers.glyphs, local_advances, num_chars);
|
||||
CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationDefault, buffers.glyphs, buffers.boxes, num_chars);
|
||||
CGFloat x = 0, y = 0;
|
||||
if (cell_width > canvas_width) goto end;
|
||||
for (size_t i = 0; i < num_chars; i++) {
|
||||
if (local_advances[i].width + x > canvas_width) {
|
||||
x = 0;
|
||||
y += cell_height;
|
||||
}
|
||||
if (y + cell_height > canvas_height) {
|
||||
num_chars = i - 1;
|
||||
break;
|
||||
}
|
||||
buffers.positions[i] = CGPointMake(x, -y);
|
||||
x += cell_width;
|
||||
}
|
||||
unsigned long height = MIN((int)ceil(y) + cell_height, canvas_height);
|
||||
ensure_render_space(canvas_width, height, num_chars);
|
||||
render_glyphs(font, canvas_width, height, baseline, num_chars);
|
||||
uint8_t r = (fg >> 16) & 0xff, g = (fg >> 8) & 0xff, b = fg & 0xff;
|
||||
for (uint8_t *p = (uint8_t*)PyBytes_AS_STRING(pbuf), *s = buffers.render_buf; p < (uint8_t*)PyBytes_AS_STRING(pbuf) + sizeof(pixel) * canvas_width * height; p += 4, s++) {
|
||||
p[0] = r; p[1] = g; p[2] = b; p[3] = s[0];
|
||||
}
|
||||
end:
|
||||
Py_INCREF(pbuf);
|
||||
return pbuf;
|
||||
|
||||
}
|
||||
|
||||
static bool
|
||||
ensure_ui_font(size_t in_height) {
|
||||
static size_t for_height = 0;
|
||||
@@ -879,6 +927,7 @@ static PyMethodDef methods[] = {
|
||||
METHODB(get_variable_data, METH_NOARGS),
|
||||
METHODB(identify_for_debug, METH_NOARGS),
|
||||
METHODB(set_size, METH_VARARGS),
|
||||
METHODB(render_sample_text, METH_VARARGS),
|
||||
METHODB(get_best_name, METH_O),
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
@@ -427,6 +427,7 @@ class Face:
|
||||
def identify_for_debug(self) -> str: ...
|
||||
def postscript_name(self) -> str: ...
|
||||
def set_size(self, sz_in_pts: float, dpi_x: float, dpi_y: float) -> None: ...
|
||||
def render_sample_text(self, text: str, width: int, height: int, fg_color: int = 0xffffff) -> bytes: ...
|
||||
|
||||
|
||||
class CoreTextFont(TypedDict):
|
||||
@@ -459,6 +460,7 @@ class CTFace:
|
||||
def identify_for_debug(self) -> str: ...
|
||||
def postscript_name(self) -> str: ...
|
||||
def set_size(self, sz_in_pts: float, dpi_x: float, dpi_y: float) -> None: ...
|
||||
def render_sample_text(self, text: str, width: int, height: int, fg_color: int = 0xffffff) -> bytes: ...
|
||||
|
||||
|
||||
def coretext_all_fonts(monospaced_only: bool) -> Tuple[CoreTextFont, ...]:
|
||||
|
||||
@@ -632,7 +632,7 @@ static PyObject* box_drawing_function = NULL, *prerender_function = NULL, *descr
|
||||
|
||||
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) {
|
||||
const pixel col = color_rgb << 8;
|
||||
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;
|
||||
const uint8_t *s = alpha_mask + src_stride * sr;
|
||||
|
||||
@@ -461,29 +461,30 @@ def shape_string(
|
||||
return test_shape(line, path)
|
||||
|
||||
|
||||
def show(outfile: str, width: int, height: int, fmt: int) -> None:
|
||||
import os
|
||||
def show(rgba_data: bytes, width: int, height: int, fmt: int = 32) -> None:
|
||||
from base64 import standard_b64encode
|
||||
|
||||
from kittens.tui.images import GraphicsCommand
|
||||
|
||||
data = memoryview(standard_b64encode(rgba_data))
|
||||
cmd = GraphicsCommand()
|
||||
cmd.a = 'T'
|
||||
cmd.f = fmt
|
||||
cmd.s = width
|
||||
cmd.v = height
|
||||
cmd.t = 't'
|
||||
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
cmd.m = 1 if data else 0
|
||||
sys.stdout.buffer.write(cmd.serialize(chunk))
|
||||
cmd.clear()
|
||||
sys.stdout.flush()
|
||||
sys.stdout.buffer.write(cmd.serialize(standard_b64encode(os.path.abspath(outfile).encode())))
|
||||
sys.stdout.buffer.flush()
|
||||
|
||||
|
||||
def display_bitmap(rgb_data: bytes, width: int, height: int) -> None:
|
||||
from tempfile import NamedTemporaryFile
|
||||
setattr(display_bitmap, 'detected', True)
|
||||
with NamedTemporaryFile(suffix='.rgba', delete=False) as f:
|
||||
f.write(rgb_data)
|
||||
assert len(rgb_data) == 4 * width * height
|
||||
show(f.name, width, height, 32)
|
||||
show(rgb_data, width, height)
|
||||
|
||||
|
||||
def test_render_string(
|
||||
|
||||
@@ -633,7 +633,7 @@ copy_color_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect
|
||||
static const bool debug_placement = false;
|
||||
|
||||
static void
|
||||
place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, size_t baseline, unsigned int glyph_num, pixel fg_rgb) {
|
||||
place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, size_t baseline, unsigned int glyph_num, pixel fg_rgb, size_t x_in_canvas, size_t y_in_canvas) {
|
||||
// We want the glyph to be positioned inside the cell based on the bearingX
|
||||
// and bearingY values, making sure that it does not overflow the cell.
|
||||
|
||||
@@ -649,6 +649,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
|
||||
uint32_t extra = dest.left + bm->width - cell_width;
|
||||
dest.left = extra > dest.left ? 0 : dest.left - extra;
|
||||
}
|
||||
dest.left += x_in_canvas;
|
||||
|
||||
// Calculate row bounds
|
||||
int32_t yoff = (ssize_t)(y_offset + bm->bitmap_top);
|
||||
@@ -657,8 +658,9 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
|
||||
} else {
|
||||
dest.top = baseline - yoff;
|
||||
}
|
||||
dest.top += y_in_canvas;
|
||||
|
||||
/* printf("x_offset: %d y_offset: %d src_start_row: %u src_start_column: %u dest_start_row: %u dest_start_column: %u bm_width: %lu bitmap_rows: %lu\n", xoff, yoff, src.top, src.left, dest.top, dest.left, bm->width, bm->rows); */
|
||||
// printf("x_offset: %d y_offset: %d src_start_row: %u src_start_column: %u dest_start_row: %u dest_start_column: %u bm_width: %lu bitmap_rows: %lu\n", xoff, yoff, src.top, src.left, dest.top, dest.left, bm->width, bm->rows);
|
||||
|
||||
if (bm->pixel_mode == FT_PIXEL_MODE_BGRA) {
|
||||
copy_color_bitmap(bm->buf, cell, &src, &dest, bm->stride, cell_width);
|
||||
@@ -698,7 +700,7 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
|
||||
y = (float)positions[i].y_offset / 64.0f;
|
||||
if (debug_placement) printf("%d: x=%f canvas: %u", i, x_offset, canvas_width);
|
||||
if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) {
|
||||
place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, baseline, i, 0xffffff);
|
||||
place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, baseline, i, 0xffffff, 0, 0);
|
||||
}
|
||||
if (debug_placement) printf(" adv: %f\n", (float)positions[i].x_advance / 64.0f);
|
||||
// the roundf() below is needed for infinite length ligatures, for a test case
|
||||
@@ -878,7 +880,7 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) {
|
||||
FT_Bitmap *bitmap = &self->face->glyph->bitmap;
|
||||
pbm = EMPTY_PBM;
|
||||
populate_processed_bitmap(self->face->glyph, bitmap, &pbm, false);
|
||||
place_bitmap_in_canvas(canvas, &pbm, canvas_width, canvas_height, pen_x, 0, baseline, n, 0xffffff);
|
||||
place_bitmap_in_canvas(canvas, &pbm, canvas_width, canvas_height, 0, 0, baseline, n, 0xffffff, pen_x, 0);
|
||||
pen_x += self->face->glyph->advance.x >> 6;
|
||||
}
|
||||
ans.width = pen_x; ans.height = canvas_height;
|
||||
@@ -923,9 +925,13 @@ render_sample_text(Face *self, PyObject *args) {
|
||||
FT_Bitmap *bitmap = &self->face->glyph->bitmap;
|
||||
ProcessedBitmap pbm = EMPTY_PBM;
|
||||
populate_processed_bitmap(self->face->glyph, bitmap, &pbm, false);
|
||||
place_bitmap_in_canvas(canvas, &pbm, cell_width, cell_height, pen_x, pen_y, baseline, 0, fg);
|
||||
place_bitmap_in_canvas(canvas, &pbm, canvas_width, canvas_height, 0, 0, baseline, 99999, fg, pen_x, pen_y);
|
||||
pen_x += self->face->glyph->advance.x >> 6;
|
||||
}
|
||||
for (uint8_t *p = (uint8_t*)PyBytes_AS_STRING(pbuf); p < (uint8_t*)PyBytes_AS_STRING(pbuf) + sizeof(pixel) * canvas_width * (MIN(canvas_height, pen_y + cell_height)); p += 4) {
|
||||
uint8_t a = p[0], b = p[1], g = p[2], r = p[3];
|
||||
p[0] = r; p[1] = g; p[2] = b; p[3] = a;
|
||||
}
|
||||
end:
|
||||
Py_INCREF(pbuf);
|
||||
return pbuf;
|
||||
|
||||
Reference in New Issue
Block a user