mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-02 12:44:01 +02:00
Work on rendering sample text for a font
This commit is contained in:
@@ -2,11 +2,16 @@
|
||||
# License: GPLv3 Copyright: 2024, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import json
|
||||
import string
|
||||
import sys
|
||||
from typing import Any
|
||||
from typing import Any, Dict, Tuple, TypedDict
|
||||
|
||||
from kitty.fonts.common import get_variable_data_for_descriptor
|
||||
from kitty.conf.utils import to_color
|
||||
from kitty.fonts import Descriptor
|
||||
from kitty.fonts.common import face_from_descriptor, get_font_files, get_variable_data_for_descriptor
|
||||
from kitty.fonts.list import create_family_groups
|
||||
from kitty.options.types import Options
|
||||
from kitty.options.utils import parse_font_spec
|
||||
|
||||
|
||||
def send_to_kitten(x: Any) -> None:
|
||||
@@ -15,7 +20,77 @@ def send_to_kitten(x: Any) -> None:
|
||||
sys.stdout.buffer.flush()
|
||||
|
||||
|
||||
class TextStyle(TypedDict):
|
||||
font_size: float
|
||||
dpi_x: float
|
||||
dpi_y: float
|
||||
foreground: str
|
||||
background: str
|
||||
|
||||
|
||||
FamilyKey = Tuple[str, ...]
|
||||
|
||||
|
||||
def opts_from_cmd(cmd: Dict[str, Any]) -> Tuple[Options, FamilyKey, float, float]:
|
||||
opts = Options()
|
||||
ts: TextStyle = cmd['text_style']
|
||||
opts.font_size = ts['font_size']
|
||||
opts.foreground = to_color(ts['foreground'])
|
||||
opts.background = to_color(ts['background'])
|
||||
family_key = []
|
||||
if 'font_family' in cmd:
|
||||
opts.font_family = parse_font_spec(cmd['font_family'])
|
||||
family_key.append(cmd['font_family'])
|
||||
if 'bold_font' in cmd:
|
||||
opts.bold_font = parse_font_spec(cmd['bold_font'])
|
||||
family_key.append(cmd['bold_font'])
|
||||
if 'italic_font' in cmd:
|
||||
opts.italic_font = parse_font_spec(cmd['italic_font'])
|
||||
family_key.append(cmd['italic_font'])
|
||||
if 'bold_italic_font' in cmd:
|
||||
opts.bold_italic_font = parse_font_spec(cmd['bold_italic_font'])
|
||||
family_key.append(cmd['bold_italic_font'])
|
||||
return opts, tuple(family_key), ts['dpi_x'], ts['dpi_y']
|
||||
|
||||
|
||||
BaseKey = Tuple[FamilyKey, int, int]
|
||||
FaceKey = Tuple[str, BaseKey]
|
||||
SAMPLE_TEXT = string.ascii_lowercase + string.digits + string.ascii_uppercase + ' ' + string.punctuation
|
||||
|
||||
|
||||
def render_face_sample(font: Descriptor, opts: Options, dpi_x: float, dpi_y: float, width: int, height: int, output_dir: str) -> str:
|
||||
face = face_from_descriptor(font)
|
||||
face.set_size(opts.font_size, dpi_x, dpi_y)
|
||||
face.render_sample_text(SAMPLE_TEXT, width, height, opts.foreground.rgb)
|
||||
|
||||
|
||||
def render_family_sample(
|
||||
opts: Options, family_key: FamilyKey, dpi_x: float, dpi_y: float, width: int, height: int, output_dir: str,
|
||||
cache: Dict[FaceKey, str]
|
||||
) -> Dict[str, str]:
|
||||
base_key: BaseKey = family_key, width, height
|
||||
ans: Dict[str, str] = {}
|
||||
font_files = get_font_files(opts)
|
||||
for x in family_key:
|
||||
key: FaceKey = x, base_key
|
||||
if x == 'font_family':
|
||||
desc = font_files['medium']
|
||||
elif x == 'bold_font':
|
||||
desc = font_files['bold']
|
||||
elif x == 'italic_font':
|
||||
desc = font_files['italic']
|
||||
elif x == 'bold_italic_font':
|
||||
desc = font_files['bi']
|
||||
cached = cache.get(key)
|
||||
if cached is not None:
|
||||
ans[x] = cached
|
||||
else:
|
||||
cache[key] = ans[x] = render_face_sample(desc, opts, dpi_x, dpi_y, width, height, output_dir)
|
||||
return ans
|
||||
|
||||
|
||||
def main() -> None:
|
||||
cache: Dict[FaceKey, str] = {}
|
||||
for line in sys.stdin.buffer:
|
||||
cmd = json.loads(line)
|
||||
action = cmd.get('action', '')
|
||||
@@ -26,5 +101,8 @@ def main() -> None:
|
||||
for descriptor in cmd['descriptors']:
|
||||
ans.append(get_variable_data_for_descriptor(descriptor))
|
||||
send_to_kitten(ans)
|
||||
elif action == 'render_family_samples':
|
||||
opts, family_key, dpi_x, dpi_y = opts_from_cmd(cmd)
|
||||
send_to_kitten(render_family_sample(opts, family_key, dpi_x, dpi_y, cmd['width'], cmd['height'], cmd['output_dir'], cache))
|
||||
else:
|
||||
raise SystemExit(f'Unknown action: {action}')
|
||||
|
||||
@@ -24,6 +24,14 @@ const (
|
||||
CHOOSING_FACES
|
||||
)
|
||||
|
||||
type TextStyle struct {
|
||||
Font_sz float64 `json:"font_size"`
|
||||
Dpi_x float64 `json:"dpi_x"`
|
||||
Dpi_y float64 `json:"dpi_y"`
|
||||
Foreground string `json:"foreground"`
|
||||
Background string `json:"background"`
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
lp *loop.Loop
|
||||
fonts map[string][]ListedFont
|
||||
@@ -33,10 +41,7 @@ type handler struct {
|
||||
mouse_state tui.MouseState
|
||||
render_count uint
|
||||
render_lines tui.RenderLines
|
||||
text_style struct {
|
||||
font_sz, dpi_x, dpi_y float64
|
||||
foreground, background string
|
||||
}
|
||||
text_style TextStyle
|
||||
|
||||
// Listing
|
||||
rl *readline.Readline
|
||||
@@ -295,21 +300,21 @@ func (h *handler) on_query_response(key, val string, valid bool) error {
|
||||
}
|
||||
switch key {
|
||||
case "font_size":
|
||||
if err := set_float(key, val, &h.text_style.font_sz); err != nil {
|
||||
if err := set_float(key, val, &h.text_style.Font_sz); err != nil {
|
||||
return err
|
||||
}
|
||||
case "dpi_x":
|
||||
if err := set_float(key, val, &h.text_style.dpi_x); err != nil {
|
||||
if err := set_float(key, val, &h.text_style.Dpi_x); err != nil {
|
||||
return err
|
||||
}
|
||||
case "dpi_y":
|
||||
if err := set_float(key, val, &h.text_style.dpi_y); err != nil {
|
||||
if err := set_float(key, val, &h.text_style.Dpi_y); err != nil {
|
||||
return err
|
||||
}
|
||||
case "foreground":
|
||||
h.text_style.foreground = val
|
||||
h.text_style.Foreground = val
|
||||
case "background":
|
||||
h.text_style.background = val
|
||||
h.text_style.Background = val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -416,14 +416,18 @@ get_glyph_width(PyObject *s, glyph_index g) {
|
||||
}
|
||||
|
||||
static float
|
||||
scaled_point_sz(FONTS_DATA_HANDLE fg) {
|
||||
return ((fg->logical_dpi_x + fg->logical_dpi_y) / 144.0) * fg->font_sz_in_pts;
|
||||
_scaled_point_sz(double font_sz_in_pts, double dpi_x, double dpi_y) {
|
||||
return ((dpi_x + dpi_y) / 144.0) * font_sz_in_pts;
|
||||
}
|
||||
|
||||
bool
|
||||
set_size_for_face(PyObject *s, unsigned int UNUSED desired_height, bool force, FONTS_DATA_HANDLE fg) {
|
||||
CTFace *self = (CTFace*)s;
|
||||
float sz = scaled_point_sz(fg);
|
||||
static float
|
||||
scaled_point_sz(FONTS_DATA_HANDLE fg) {
|
||||
return _scaled_point_sz(fg->font_sz_in_pts, fg->logical_dpi_x, fg->logical_dpi_y);
|
||||
}
|
||||
|
||||
static bool
|
||||
_set_size_for_face(CTFace *self, bool force, double font_sz_in_pts, double dpi_x, double dpi_y) {
|
||||
float sz = _scaled_point_sz(font_sz_in_pts, dpi_x, dpi_y);
|
||||
if (!force && self->scaled_point_sz == sz) return true;
|
||||
RAII_CoreFoundation(CTFontRef, new_font, CTFontCreateCopyWithAttributes(self->ct_font, sz, NULL, NULL));
|
||||
if (new_font == NULL) fatal("Out of memory");
|
||||
@@ -431,6 +435,20 @@ set_size_for_face(PyObject *s, unsigned int UNUSED desired_height, bool force, F
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
set_size_for_face(PyObject *s, unsigned int UNUSED desired_height, bool force, FONTS_DATA_HANDLE fg) {
|
||||
CTFace *self = (CTFace*)s;
|
||||
return _set_size_for_face(self, force, fg->font_sz_in_pts, fg->logical_dpi_x, fg->logical_dpi_y);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
set_size(CTFace *self, PyObject *args) {
|
||||
double font_sz_in_pts, dpi_x, dpi_y;
|
||||
if (!PyArg_ParseTuple(args, "ddd", &font_sz_in_pts, &dpi_x, &dpi_y)) return NULL;
|
||||
if (!_set_size_for_face(self, false, font_sz_in_pts, dpi_x, dpi_y)) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
hb_font_t*
|
||||
harfbuzz_font_for_face(PyObject* s) {
|
||||
CTFace *self = (CTFace*)s;
|
||||
@@ -777,7 +795,7 @@ do_render(CTFontRef ct_font, unsigned int units_per_em, bool bold, bool italic,
|
||||
} else {
|
||||
render_glyphs(ct_font, canvas_width, cell_height, baseline, num_glyphs);
|
||||
Region src = {.bottom=cell_height, .right=canvas_width}, dest = {.bottom=cell_height, .right=canvas_width};
|
||||
render_alpha_mask(buffers.render_buf, canvas, &src, &dest, canvas_width, canvas_width);
|
||||
render_alpha_mask(buffers.render_buf, canvas, &src, &dest, canvas_width, canvas_width, 0xffffff);
|
||||
}
|
||||
if (num_cells && (center_glyph || (num_cells == 2 && *was_colored))) {
|
||||
if (debug_rendering) printf("centering glyphs: center_glyph: %d\n", center_glyph);
|
||||
@@ -860,6 +878,7 @@ static PyMethodDef methods[] = {
|
||||
METHODB(postscript_name, METH_NOARGS),
|
||||
METHODB(get_variable_data, METH_NOARGS),
|
||||
METHODB(identify_for_debug, METH_NOARGS),
|
||||
METHODB(set_size, METH_VARARGS),
|
||||
METHODB(get_best_name, METH_O),
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
@@ -426,6 +426,7 @@ class Face:
|
||||
def get_variable_data(self) -> VariableData: ...
|
||||
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: ...
|
||||
|
||||
|
||||
class CoreTextFont(TypedDict):
|
||||
@@ -457,6 +458,7 @@ class CTFace:
|
||||
def get_variable_data(self) -> VariableData: ...
|
||||
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 coretext_all_fonts(monospaced_only: bool) -> Tuple[CoreTextFont, ...]:
|
||||
|
||||
@@ -631,14 +631,15 @@ END_ALLOW_CASE_RANGE
|
||||
static PyObject* box_drawing_function = NULL, *prerender_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) {
|
||||
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;
|
||||
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;
|
||||
for(size_t sc = src_rect->left, dc = dest_rect->left; sc < src_rect->right && dc < dest_rect->right; sc++, dc++) {
|
||||
uint8_t src_alpha = d[dc] & 0xff;
|
||||
uint8_t alpha = s[sc];
|
||||
d[dc] = 0xffffff00 | MAX(alpha, src_alpha);
|
||||
d[dc] = col | MAX(alpha, src_alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -662,7 +663,7 @@ render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
|
||||
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0));
|
||||
ensure_canvas_can_fit(fg, 1);
|
||||
Region r = { .right = fg->cell_width, .bottom = fg->cell_height };
|
||||
render_alpha_mask(alpha_mask, fg->canvas.buf, &r, &r, fg->cell_width, fg->cell_width);
|
||||
render_alpha_mask(alpha_mask, fg->canvas.buf, &r, &r, fg->cell_width, fg->cell_width, 0xffffff);
|
||||
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp->x, sp->y, sp->z, fg->canvas.buf);
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
@@ -1465,7 +1466,7 @@ send_prerendered_sprites(FontGroup *fg) {
|
||||
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(cell_addresses, i));
|
||||
ensure_canvas_can_fit(fg, 1); // clear canvas
|
||||
Region r = { .right = fg->cell_width, .bottom = fg->cell_height };
|
||||
render_alpha_mask(alpha_mask, fg->canvas.buf, &r, &r, fg->cell_width, fg->cell_width);
|
||||
render_alpha_mask(alpha_mask, fg->canvas.buf, &r, &r, fg->cell_width, fg->cell_width, 0xffffff);
|
||||
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, x, y, z, fg->canvas.buf);
|
||||
}
|
||||
Py_CLEAR(args);
|
||||
|
||||
@@ -35,7 +35,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);
|
||||
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_line(FONTS_DATA_HANDLE, Line *line, index_type lnum, Cursor *cursor, DisableLigature);
|
||||
void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len);
|
||||
typedef void (*free_extra_data_func)(void*);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2024, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Sequence, Union
|
||||
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Sequence, TypedDict, Union
|
||||
|
||||
from kitty.constants import is_macos
|
||||
from kitty.options.types import Options
|
||||
@@ -14,14 +14,14 @@ if TYPE_CHECKING:
|
||||
|
||||
FontCollectionMapType = Literal['family_map', 'ps_map', 'full_map']
|
||||
FontMap = Dict[FontCollectionMapType, Dict[str, List[Descriptor]]]
|
||||
def Face(descriptor: Descriptor) -> Union[FT_Face, CTFace]:
|
||||
pass
|
||||
Face = Union[FT_Face, CTFace]
|
||||
def all_fonts_map(monospaced: bool) -> FontMap: ...
|
||||
def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer: ...
|
||||
def find_best_match(
|
||||
family: str, bold: bool = False, italic: bool = False, monospaced: bool = True, ignore_face: Optional[Descriptor] = None
|
||||
) -> Descriptor: ...
|
||||
def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> Descriptor: ...
|
||||
def face_from_descriptor(descriptor: Descriptor) -> Face: ...
|
||||
else:
|
||||
FontCollectionMapType = FontMap = None
|
||||
if is_macos:
|
||||
@@ -32,6 +32,7 @@ else:
|
||||
from kitty.fast_data_types import Face
|
||||
|
||||
from .fontconfig import all_fonts_map, create_scorer, find_best_match, find_last_resort_text_font
|
||||
def face_from_descriptor(descriptor: Descriptor) -> Face: return Face(descriptor=descriptor)
|
||||
|
||||
|
||||
cache_for_variable_data_by_path: Dict[str, VariableData] = {}
|
||||
@@ -40,10 +41,10 @@ attr_map = {(False, False): 'font_family', (True, False): 'bold_font', (False, T
|
||||
|
||||
def get_variable_data_for_descriptor(d: Descriptor) -> VariableData:
|
||||
if not d['path']:
|
||||
return Face(descriptor=d).get_variable_data()
|
||||
return face_from_descriptor(d).get_variable_data()
|
||||
ans = cache_for_variable_data_by_path.get(d['path'])
|
||||
if ans is None:
|
||||
ans = cache_for_variable_data_by_path[d['path']] = Face(descriptor=d).get_variable_data()
|
||||
ans = cache_for_variable_data_by_path[d['path']] = face_from_descriptor(d).get_variable_data()
|
||||
return ans
|
||||
|
||||
|
||||
@@ -92,7 +93,7 @@ def apply_variation_to_pattern(pat: Descriptor, spec: FontSpec) -> Descriptor:
|
||||
if not pat['variable']:
|
||||
return pat
|
||||
|
||||
vd = Face(descriptor=pat).get_variable_data()
|
||||
vd = face_from_descriptor(pat).get_variable_data()
|
||||
if spec.style:
|
||||
q = spec.style.lower()
|
||||
for i, ns in enumerate(vd['named_styles']):
|
||||
@@ -148,7 +149,14 @@ def get_font_from_spec(
|
||||
return find_best_match(family, bold, italic, ignore_face=resolved_medium_font)
|
||||
|
||||
|
||||
def get_font_files(opts: Options) -> Dict[str, Descriptor]:
|
||||
class FontFiles(TypedDict):
|
||||
medium: Descriptor
|
||||
bold: Descriptor
|
||||
italic: Descriptor
|
||||
bi: Descriptor
|
||||
|
||||
|
||||
def get_font_files(opts: Options) -> FontFiles:
|
||||
ans: Dict[str, Descriptor] = {}
|
||||
medium_font = get_font_from_spec(opts.font_family)
|
||||
kd = {(False, False): 'medium', (True, False): 'bold', (False, True): 'italic', (True, True): 'bi'}
|
||||
@@ -159,4 +167,4 @@ def get_font_files(opts: Options) -> Dict[str, Descriptor]:
|
||||
font = medium_font
|
||||
key = kd[(bold, italic)]
|
||||
ans[key] = font
|
||||
return ans
|
||||
return {'medium': ans['medium'], 'bold': ans['bold'], 'italic': ans['italic'], 'bi': ans['bi']}
|
||||
|
||||
@@ -5,7 +5,7 @@ import ctypes
|
||||
import sys
|
||||
from functools import partial
|
||||
from math import ceil, cos, floor, pi
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional, Tuple, Union, cast
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Literal, Optional, Tuple, Union, cast
|
||||
|
||||
from kitty.constants import is_macos
|
||||
from kitty.fast_data_types import (
|
||||
@@ -176,7 +176,7 @@ def set_font_family(opts: Optional[Options] = None, override_font_size: Optional
|
||||
sz = override_font_size or opts.font_size
|
||||
font_map = get_font_files(opts)
|
||||
current_faces = [(font_map['medium'], False, False)]
|
||||
ftypes = 'bold italic bi'.split()
|
||||
ftypes: List[Literal['bold', 'italic', 'bi']] = ['bold', 'italic', 'bi']
|
||||
indices = {k: 0 for k in ftypes}
|
||||
for k in ftypes:
|
||||
if k in font_map:
|
||||
|
||||
@@ -197,6 +197,18 @@ set_size_for_face(PyObject *s, unsigned int desired_height, bool force, FONTS_DA
|
||||
return set_font_size(self, w, w, xdpi, ydpi, desired_height, fg->cell_height);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
set_size(Face *self, PyObject *args) {
|
||||
double font_sz_in_pts, dpi_x, dpi_y;
|
||||
if (!PyArg_ParseTuple(args, "ddd", &font_sz_in_pts, &dpi_x, &dpi_y)) return NULL;
|
||||
FT_F26Dot6 w = (FT_F26Dot6)(ceil(font_sz_in_pts * 64.0));
|
||||
FT_UInt xdpi = (FT_UInt)dpi_x, ydpi = (FT_UInt)dpi_y;
|
||||
if (self->char_width == w && self->char_height == w && self->xdpi == xdpi && self->ydpi == ydpi) { Py_RETURN_NONE; }
|
||||
self->size_in_pts = (float)font_sz_in_pts;
|
||||
if (!set_font_size(self, w, w, xdpi, ydpi, 0, 0)) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static bool
|
||||
init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_HANDLE fg) {
|
||||
#define CPY(n) self->n = self->face->n;
|
||||
@@ -621,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) {
|
||||
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) {
|
||||
// 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.
|
||||
|
||||
@@ -650,7 +662,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
|
||||
|
||||
if (bm->pixel_mode == FT_PIXEL_MODE_BGRA) {
|
||||
copy_color_bitmap(bm->buf, cell, &src, &dest, bm->stride, cell_width);
|
||||
} else render_alpha_mask(bm->buf, cell, &src, &dest, bm->stride, cell_width);
|
||||
} else render_alpha_mask(bm->buf, cell, &src, &dest, bm->stride, cell_width, fg_rgb);
|
||||
}
|
||||
|
||||
static const ProcessedBitmap EMPTY_PBM = {.factor = 1};
|
||||
@@ -686,7 +698,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);
|
||||
place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, baseline, i, 0xffffff);
|
||||
}
|
||||
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
|
||||
@@ -866,7 +878,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);
|
||||
place_bitmap_in_canvas(canvas, &pbm, canvas_width, canvas_height, pen_x, 0, baseline, n, 0xffffff);
|
||||
pen_x += self->face->glyph->advance.x >> 6;
|
||||
}
|
||||
ans.width = pen_x; ans.height = canvas_height;
|
||||
@@ -882,6 +894,44 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
render_sample_text(Face *self, PyObject *args) {
|
||||
unsigned long canvas_width, canvas_height, pen_x = 0, pen_y = 0;
|
||||
unsigned long fg = 0xffffff;
|
||||
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);
|
||||
pixel *canvas = (pixel*)PyBytes_AS_STRING(pbuf);
|
||||
if (cell_width > canvas_width) goto end;
|
||||
|
||||
for (ssize_t n = 0; n < PyUnicode_GET_LENGTH(ptext); n++) {
|
||||
if (pen_x + cell_width > canvas_width) {
|
||||
pen_y += cell_height;
|
||||
pen_x = 0;
|
||||
}
|
||||
if (pen_y + cell_height > canvas_height) break;
|
||||
Py_UCS4 ch = PyUnicode_READ_CHAR(ptext, n);
|
||||
FT_UInt glyph_index = FT_Get_Char_Index(self->face, ch);
|
||||
if (!glyph_index) continue;
|
||||
int error = FT_Load_Glyph(self->face, glyph_index, FT_LOAD_DEFAULT);
|
||||
if (error) continue;
|
||||
error = FT_Render_Glyph(self->face->glyph, FT_RENDER_MODE_NORMAL);
|
||||
if (error) continue;
|
||||
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);
|
||||
pen_x += self->face->glyph->advance.x >> 6;
|
||||
}
|
||||
end:
|
||||
Py_INCREF(pbuf);
|
||||
return pbuf;
|
||||
}
|
||||
|
||||
|
||||
// Boilerplate {{{
|
||||
|
||||
static PyMemberDef members[] = {
|
||||
@@ -910,6 +960,8 @@ static PyMethodDef methods[] = {
|
||||
METHODB(extra_data, METH_NOARGS),
|
||||
METHODB(get_variable_data, METH_NOARGS),
|
||||
METHODB(get_best_name, METH_O),
|
||||
METHODB(set_size, METH_VARARGS),
|
||||
METHODB(render_sample_text, METH_VARARGS),
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user