More work on line based rendering
Should now build on all platforms
This commit is contained in:
parent
d873d11202
commit
38a5e76c50
@ -118,18 +118,7 @@ coretext_all_fonts(PyObject UNUSED *_self) {
|
||||
}
|
||||
|
||||
static inline bool
|
||||
apply_size(Face *self, float point_sz, float dpi) {
|
||||
self->point_sz = point_sz;
|
||||
self->dpi = dpi;
|
||||
self->scaled_point_sz = (dpi / 72.0) * point_sz;
|
||||
if (self->font) {
|
||||
CTFontRef f = CTFontCreateCopyWithAttributes(self->font, self->scaled_point_sz, NULL, NULL);
|
||||
CFRelease(self->font);
|
||||
self->font = f;
|
||||
} else {
|
||||
self->font = CTFontCreateWithFontDescriptor(self->descriptor, self->scaled_point_sz, NULL);
|
||||
}
|
||||
if (self->font == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create or copy font object"); return false; }
|
||||
init_font(Face *self) {
|
||||
self->units_per_em = CTFontGetUnitsPerEm(self->font);
|
||||
self->ascent = CTFontGetAscent(self->font);
|
||||
self->descent = CTFontGetDescent(self->font);
|
||||
@ -151,6 +140,34 @@ apply_size(Face *self, float point_sz, float dpi) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
apply_size(Face *self, float point_sz, float dpi) {
|
||||
self->point_sz = point_sz;
|
||||
self->dpi = dpi;
|
||||
self->scaled_point_sz = (dpi / 72.0) * point_sz;
|
||||
if (self->font) {
|
||||
CTFontRef f = CTFontCreateCopyWithAttributes(self->font, self->scaled_point_sz, NULL, NULL);
|
||||
CFRelease(self->font);
|
||||
self->font = f;
|
||||
} else {
|
||||
self->font = CTFontCreateWithFontDescriptor(self->descriptor, self->scaled_point_sz, NULL);
|
||||
}
|
||||
if (self->font == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create or copy font object"); return false; }
|
||||
return init_font(self);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
init_font_names(Face *self) {
|
||||
self->family_name = convert_cfstring(CTFontCopyFamilyName(self->font), 1);
|
||||
self->full_name = convert_cfstring(CTFontCopyFullName(self->font), 1);
|
||||
self->postscript_name = convert_cfstring(CTFontCopyPostScriptName(self->font), 1);
|
||||
NSURL *url = (NSURL*)CTFontCopyAttribute(self->font, kCTFontURLAttribute);
|
||||
self->path = PyUnicode_FromString([[url path] UTF8String]);
|
||||
[url release];
|
||||
if (self->family_name == NULL || self->full_name == NULL || self->postscript_name == NULL || self->path == NULL) {return false;}
|
||||
return true;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
Face *self;
|
||||
@ -163,13 +180,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
if (desc) {
|
||||
self->descriptor = desc;
|
||||
if (apply_size(self, point_sz, dpi)) {
|
||||
self->family_name = convert_cfstring(CTFontCopyFamilyName(self->font), 1);
|
||||
self->full_name = convert_cfstring(CTFontCopyFullName(self->font), 1);
|
||||
self->postscript_name = convert_cfstring(CTFontCopyPostScriptName(self->font), 1);
|
||||
NSURL *url = (NSURL*)CTFontCopyAttribute(self->font, kCTFontURLAttribute);
|
||||
self->path = PyUnicode_FromString([[url path] UTF8String]);
|
||||
[url release];
|
||||
if (self->family_name == NULL || self->full_name == NULL || self->postscript_name == NULL || self->path == NULL) { Py_CLEAR(self); }
|
||||
if (!init_font_names(self)) Py_CLEAR(self);
|
||||
} else Py_CLEAR(self);
|
||||
} else {
|
||||
Py_CLEAR(self);
|
||||
@ -209,6 +220,32 @@ face_has_codepoint(PyObject *s, char_type ch) {
|
||||
return CTFontGetGlyphsForCharacters(self->font, chars, glyphs, count);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
face_for_text(Face *self, PyObject *args) {
|
||||
char *s;
|
||||
int bold, italic;
|
||||
Face *ans = NULL;
|
||||
if (!PyArg_ParseTuple(args, "espp", "UTF-8", &s, &bold, &italic)) return NULL;
|
||||
CFStringRef str = CFStringCreateWithCString(NULL, s, kCFStringEncodingUTF8);
|
||||
if (!str) return PyErr_NoMemory();
|
||||
unichar chars[10] = {0};
|
||||
CFRange range = CFRangeMake(0, CFStringGetLength(str));
|
||||
CFStringGetCharacters(str, range, chars);
|
||||
CTFontRef font = CTFontCreateForString(self->font, str, range);
|
||||
if (font == self->font) return (PyObject*)self;
|
||||
if (font == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to find fallback font"); goto end; }
|
||||
ans = (Face *)Face_Type.tp_alloc(&Face_Type, 0);
|
||||
if (ans == NULL) { PyErr_NoMemory(); goto end; }
|
||||
ans->font = font;
|
||||
if (!init_font(ans) || !init_font_names(ans)) { Py_CLEAR(ans); goto end; }
|
||||
ans->point_sz = self->point_sz;
|
||||
ans->dpi = self->dpi;
|
||||
end:
|
||||
CFRelease(str);
|
||||
if (ans) return (PyObject*)ans;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline void
|
||||
calc_cell_size(Face *self, unsigned int *cell_width, unsigned int *cell_height) {
|
||||
#define count (128 - 32)
|
||||
@ -284,6 +321,7 @@ static PyMemberDef members[] = {
|
||||
};
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
METHODB(face_for_text, METH_VARARGS),
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
@ -2,17 +2,17 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import ctypes
|
||||
import re
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
from kitty.fast_data_types import CTFace as Face, coretext_all_fonts
|
||||
from kitty.utils import ceil_int, get_logical_dpi, safe_print, wcwidth, adjust_line_height
|
||||
from kitty.utils import safe_print
|
||||
|
||||
main_font = {}
|
||||
symbol_map = {}
|
||||
cell_width = cell_height = baseline = CellTexture = WideCellTexture = underline_thickness = underline_position = None
|
||||
attr_map = {(False, False): 'font_family', (True, False): 'bold_font', (False, True): 'italic_font', (True, True): 'bold_italic_font'}
|
||||
attr_map = {(False, False): 'font_family',
|
||||
(True, False): 'bold_font',
|
||||
(False, True): 'italic_font',
|
||||
(True, True): 'bold_italic_font'}
|
||||
|
||||
|
||||
def create_font_map(all_fonts):
|
||||
@ -27,11 +27,21 @@ def create_font_map(all_fonts):
|
||||
return ans
|
||||
|
||||
|
||||
def find_best_match(font_map, family, bold, italic):
|
||||
def all_fonts_map():
|
||||
ans = getattr(all_fonts_map, 'ans', None)
|
||||
if ans is None:
|
||||
ans = all_fonts_map.ans = create_font_map(coretext_all_fonts())
|
||||
return ans
|
||||
|
||||
|
||||
def find_best_match(family, bold, italic):
|
||||
q = re.sub(r'\s+', ' ', family.lower())
|
||||
font_map = all_fonts_map()
|
||||
|
||||
def score(candidate):
|
||||
style_match = 1 if candidate['bold'] == bold and candidate['italic'] == italic else 0
|
||||
style_match = 1 if candidate['bold'] == bold and candidate[
|
||||
'italic'
|
||||
] == italic else 0
|
||||
monospace_match = 1 if candidate['monospace'] else 0
|
||||
return style_match, monospace_match
|
||||
|
||||
@ -45,7 +55,10 @@ def find_best_match(font_map, family, bold, italic):
|
||||
# Let CoreText choose the font if the family exists, otherwise
|
||||
# fallback to Menlo
|
||||
if q not in font_map['family_map']:
|
||||
safe_print('The font {} was not found, falling back to Menlo'.format(family), file=sys.stderr)
|
||||
safe_print(
|
||||
'The font {} was not found, falling back to Menlo'.format(family),
|
||||
file=sys.stderr
|
||||
)
|
||||
family = 'Menlo'
|
||||
return {
|
||||
'monospace': True,
|
||||
@ -55,53 +68,65 @@ def find_best_match(font_map, family, bold, italic):
|
||||
}
|
||||
|
||||
|
||||
def get_face(font_map, family, main_family, font_size, dpi, bold=False, italic=False):
|
||||
def resolve_family(f):
|
||||
if (bold or italic) and f == 'auto':
|
||||
f = main_family
|
||||
if f.lower() == 'monospace':
|
||||
f = 'Menlo'
|
||||
return f
|
||||
descriptor = find_best_match(font_map, resolve_family(family), bold, italic)
|
||||
def resolve_family(f, main_family, bold, italic):
|
||||
if (bold or italic) and f == 'auto':
|
||||
f = main_family
|
||||
if f.lower() == 'monospace':
|
||||
f = 'Menlo'
|
||||
return f
|
||||
|
||||
|
||||
FaceDescription = namedtuple(
|
||||
'FaceDescription', 'resolved_family family bold italic'
|
||||
)
|
||||
|
||||
|
||||
def face_description(family, main_family, bold=False, italic=False):
|
||||
return FaceDescription(
|
||||
resolve_family(family, main_family, bold, italic), family, bold, italic
|
||||
)
|
||||
|
||||
|
||||
def get_face(family, font_size, dpi, bold=False, italic=False):
|
||||
descriptor = find_best_match(family, bold, italic)
|
||||
return Face(descriptor, font_size, dpi)
|
||||
|
||||
|
||||
def install_symbol_map(all_fonts, val, font_size, dpi):
|
||||
global symbol_map
|
||||
symbol_map = {}
|
||||
family_map = {f: get_face(all_fonts, f, 'Menlo', font_size, dpi) for f in set(val.values())}
|
||||
for ch, family in val.items():
|
||||
symbol_map[ch] = family_map[family]
|
||||
|
||||
|
||||
def set_font_family(opts, override_font_size=None, ignore_dpi_failure=False):
|
||||
global cell_width, cell_height, baseline, CellTexture, WideCellTexture, underline_thickness, underline_position
|
||||
try:
|
||||
dpi = get_logical_dpi()
|
||||
except Exception:
|
||||
if not ignore_dpi_failure:
|
||||
raise
|
||||
dpi = (72, 72) # Happens when running via develop() in an ssh session
|
||||
dpi = sum(dpi) / 2.0
|
||||
font_size = override_font_size or opts.font_size
|
||||
all_fonts = create_font_map(coretext_all_fonts())
|
||||
|
||||
def get_font_files(opts):
|
||||
ans = {}
|
||||
for (bold, italic), attr in attr_map.items():
|
||||
main_font[(bold, italic)] = get_face(all_fonts, getattr(opts, attr), opts.font_family, font_size, dpi, bold, italic)
|
||||
|
||||
install_symbol_map(all_fonts, opts.symbol_map, font_size, dpi)
|
||||
mf = main_font[(False, False)]
|
||||
cell_width, cell_height = mf.cell_size()
|
||||
cell_height = adjust_line_height(cell_height, opts.adjust_line_height)
|
||||
CellTexture = ctypes.c_ubyte * (cell_width * cell_height)
|
||||
WideCellTexture = ctypes.c_ubyte * (2 * cell_width * cell_height)
|
||||
baseline = int(round(mf.ascent))
|
||||
underline_position = int(round(baseline - mf.underline_position))
|
||||
underline_thickness = ceil_int(mf.underline_thickness)
|
||||
return cell_width, cell_height
|
||||
face = face_description(
|
||||
getattr(opts, attr), opts.font_family, bold, italic
|
||||
)
|
||||
key = {(False, False): 'medium',
|
||||
(True, False): 'bold',
|
||||
(False, True): 'italic',
|
||||
(True, True): 'bi'}[(bold, italic)]
|
||||
ans[key] = face
|
||||
if key == 'medium':
|
||||
save_medium_face.family = face.resolved_family
|
||||
return ans
|
||||
|
||||
|
||||
def test_font_matching(name='Menlo', bold=False, italic=False, dpi=72.0, font_size=11.0):
|
||||
def face_from_font(font, pt_sz, xdpi, ydpi):
|
||||
return get_face(font.resolved_family, pt_sz, (xdpi + ydpi) / 2, bold=font.bold, italic=font.italic)
|
||||
|
||||
|
||||
def save_medium_face(face, family):
|
||||
save_medium_face.face = face
|
||||
|
||||
|
||||
def font_for_text(text, current_font_family, pt_sz, xdpi, ydpi, bold=False, italic=False):
|
||||
save_medium_face.face.face_for_text(text, bold, italic)
|
||||
|
||||
|
||||
def font_for_family(family):
|
||||
return face_description(family, save_medium_face.family)
|
||||
|
||||
|
||||
def test_font_matching(
|
||||
name='Menlo', bold=False, italic=False, dpi=72.0, font_size=11.0
|
||||
):
|
||||
all_fonts = create_font_map(coretext_all_fonts())
|
||||
face = get_face(all_fonts, name, 'Menlo', font_size, dpi, bold, italic)
|
||||
return face
|
||||
@ -111,55 +136,7 @@ def test_family_matching(name='Menlo', dpi=72.0, font_size=11.0):
|
||||
all_fonts = create_font_map(coretext_all_fonts())
|
||||
for bold in (False, True):
|
||||
for italic in (False, True):
|
||||
face = get_face(all_fonts, name, 'Menlo', font_size, dpi, bold, italic)
|
||||
face = get_face(
|
||||
all_fonts, name, 'Menlo', font_size, dpi, bold, italic
|
||||
)
|
||||
print(bold, italic, face)
|
||||
|
||||
|
||||
def current_cell():
|
||||
return CellTexture, cell_width, cell_height, baseline, underline_thickness, underline_position
|
||||
|
||||
|
||||
def split(buf, cell_width, cell_height):
|
||||
first, second = CellTexture(), CellTexture()
|
||||
for y in range(cell_height):
|
||||
offset, woffset = y * cell_width, y * cell_width * 2
|
||||
for x in range(cell_width):
|
||||
first[offset + x] = buf[woffset + x]
|
||||
second[offset + x] = buf[woffset + cell_width + x]
|
||||
return first, second
|
||||
|
||||
|
||||
def render_cell(text=' ', bold=False, italic=False):
|
||||
ch = text[0]
|
||||
width = wcwidth(ch)
|
||||
face = symbol_map.get(ch) or main_font[(bold, italic)]
|
||||
if width == 2:
|
||||
buf, width = WideCellTexture(), cell_width * 2
|
||||
else:
|
||||
buf, width = CellTexture(), cell_width
|
||||
face.render_char(text, width, cell_height, ctypes.addressof(buf))
|
||||
if width == 2:
|
||||
first, second = split(buf, cell_width, cell_height)
|
||||
else:
|
||||
first, second = buf, None
|
||||
return first, second
|
||||
|
||||
|
||||
def develop(family='monospace', sz=288):
|
||||
import pickle
|
||||
from .render import render_string
|
||||
from kitty.fast_data_types import glfw_init
|
||||
from kitty.config import defaults
|
||||
import os
|
||||
glfw_init()
|
||||
try:
|
||||
os.remove('/tmp/cell.data')
|
||||
except EnvironmentError:
|
||||
pass
|
||||
opts = defaults._replace(font_family=family, font_size=sz)
|
||||
set_font_family(opts, ignore_dpi_failure=True)
|
||||
for (bold, italic), face in main_font.items():
|
||||
print('bold: {} italic: {} {}'.format(bold, italic, face))
|
||||
print('cell_width: {}, cell_height: {}, baseline: {}'.format(cell_width, cell_height, baseline))
|
||||
buf, w, h = render_string()
|
||||
open('/tmp/cell.data', 'wb').write(pickle.dumps((bytearray(buf), w, h)))
|
||||
|
||||
@ -11,10 +11,13 @@ from kitty.utils import get_logical_dpi
|
||||
from kitty.fast_data_types import set_font, set_font_size
|
||||
from .box_drawing import render_box_char, is_renderable_box_char
|
||||
if isosx:
|
||||
pass
|
||||
from .core_text import get_font_files, font_for_text, face_from_font, font_for_family, save_medium_face
|
||||
else:
|
||||
from .fontconfig import get_font_files, font_for_text, face_from_font, font_for_family
|
||||
|
||||
def save_medium_face(f):
|
||||
pass
|
||||
|
||||
|
||||
def create_face(font):
|
||||
s = set_font_family.state
|
||||
@ -50,6 +53,7 @@ def set_font_family(opts, override_font_size=None):
|
||||
set_font_family.state = FontState('', sz, xdpi, ydpi, 0, 0, 0, 0, 0)
|
||||
font_map = get_font_files(opts)
|
||||
faces = [create_face(font_map['medium'])]
|
||||
save_medium_face(faces[0])
|
||||
for k in 'bold italic bi'.split():
|
||||
if k in font_map:
|
||||
faces.append(create_face(font_map[k]))
|
||||
@ -103,11 +107,15 @@ def add_curl(buf, cell_width, position, thickness, cell_height):
|
||||
|
||||
|
||||
def render_cell(text=' ', bold=False, italic=False, underline=0, strikethrough=False):
|
||||
CharTexture, cell_width, cell_height, baseline, underline_thickness, underline_position = current_cell()
|
||||
s = set_font_family.state
|
||||
cell_width, cell_height, baseline = s.cell_width, s.cell_height, s.baseline
|
||||
underline_thickness, underline_position = s.underline_thickness, s.underline_position
|
||||
CharTexture = ctypes.c_ubyte * cell_width * cell_height
|
||||
if is_renderable_box_char(text):
|
||||
first, second = render_box_char(text, CharTexture(), cell_width, cell_height), None
|
||||
else:
|
||||
first, second = rc(text, bold, italic)
|
||||
first = CharTexture()
|
||||
second = None
|
||||
|
||||
def dl(f, *a):
|
||||
f(first, cell_width, *a)
|
||||
@ -181,7 +189,8 @@ def render_string(text='\'Qing👁a⧽', underline=2, strikethrough=True):
|
||||
current_text = c
|
||||
if current_text:
|
||||
render_one(current_text)
|
||||
cell_width, cell_height = current_cell()[1:3]
|
||||
s = set_font_family.state
|
||||
cell_width, cell_height = s.cell_width, s.cell_height
|
||||
char_data = join_cells(cell_width, cell_height, *cells)
|
||||
return char_data, cell_width * len(cells), cell_height
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user