macOS: Fix selecting fonts using full names (including sub-family) not working
Fixes #83
This commit is contained in:
parent
58644e2b37
commit
697a7b78b3
@ -22,18 +22,19 @@ typedef struct {
|
|||||||
unsigned int units_per_em;
|
unsigned int units_per_em;
|
||||||
float ascent, descent, leading, underline_position, underline_thickness, point_sz, scaled_point_sz;
|
float ascent, descent, leading, underline_position, underline_thickness, point_sz, scaled_point_sz;
|
||||||
CTFontRef font;
|
CTFontRef font;
|
||||||
PyObject *family_name, *full_name, *postscript_name;
|
PyObject *family_name, *full_name, *postscript_name, *path;
|
||||||
} Face;
|
} Face;
|
||||||
|
|
||||||
|
|
||||||
static PyObject*
|
static inline PyObject*
|
||||||
convert_cfstring(CFStringRef src) {
|
convert_cfstring(CFStringRef src, int free_src) {
|
||||||
#define SZ 2048
|
#define SZ 2048
|
||||||
static char buf[SZ+2] = {0};
|
static char buf[SZ+2] = {0};
|
||||||
const char *p = CFStringGetCStringPtr(src, kCFStringEncodingUTF8);
|
PyObject *ans = NULL;
|
||||||
if (p != NULL) return PyUnicode_FromString(buf);
|
if(!CFStringGetCString(src, buf, SZ, kCFStringEncodingUTF8)) PyErr_SetString(PyExc_ValueError, "Failed to convert CFString");
|
||||||
if(!CFStringGetCString(src, buf, SZ, kCFStringEncodingUTF8)) { PyErr_SetString(PyExc_ValueError, "Failed to convert CFString"); return NULL; }
|
else ans = PyUnicode_FromString(buf);
|
||||||
return PyUnicode_FromString(buf);
|
if (free_src) CFRelease(src);
|
||||||
|
return ans;
|
||||||
#undef SZ
|
#undef SZ
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +71,34 @@ font_descriptor_to_python(CTFontDescriptorRef descriptor) {
|
|||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CTFontDescriptorRef
|
||||||
|
font_descriptor_from_python(PyObject *src) {
|
||||||
|
CTFontSymbolicTraits symbolic_traits = 0;
|
||||||
|
NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
|
||||||
|
PyObject *t = PyDict_GetItemString(src, "traits");
|
||||||
|
if (t == NULL) {
|
||||||
|
symbolic_traits = (
|
||||||
|
(PyDict_GetItemString(src, "bold") == Py_True) ? kCTFontBoldTrait : 0 |
|
||||||
|
(PyDict_GetItemString(src, "italic") == Py_True) ? kCTFontItalicTrait : 0 |
|
||||||
|
(PyDict_GetItemString(src, "monospace") == Py_True) ? kCTFontMonoSpaceTrait : 0);
|
||||||
|
} else {
|
||||||
|
symbolic_traits = PyLong_AsUnsignedLong(t);
|
||||||
|
}
|
||||||
|
NSDictionary *traits = @{(id)kCTFontSymbolicTrait:[NSNumber numberWithUnsignedInt:symbolic_traits]};
|
||||||
|
attrs[(id)kCTFontTraitsAttribute] = traits;
|
||||||
|
|
||||||
|
#define SET(x, attr) \
|
||||||
|
t = PyDict_GetItemString(src, #x); \
|
||||||
|
if (t) attrs[(id)attr] = [NSString stringWithUTF8String:PyUnicode_AsUTF8(t)];
|
||||||
|
|
||||||
|
SET(family, kCTFontFamilyNameAttribute);
|
||||||
|
SET(style, kCTFontStyleNameAttribute);
|
||||||
|
SET(postscript_name, kCTFontNameAttribute);
|
||||||
|
#undef SET
|
||||||
|
|
||||||
|
return CTFontDescriptorCreateWithAttributes((CFDictionaryRef) attrs);
|
||||||
|
}
|
||||||
|
|
||||||
PyObject*
|
PyObject*
|
||||||
coretext_all_fonts(PyObject UNUSED *_self) {
|
coretext_all_fonts(PyObject UNUSED *_self) {
|
||||||
static CTFontCollectionRef collection = NULL;
|
static CTFontCollectionRef collection = NULL;
|
||||||
@ -89,23 +118,17 @@ coretext_all_fonts(PyObject UNUSED *_self) {
|
|||||||
static PyObject*
|
static PyObject*
|
||||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||||
Face *self;
|
Face *self;
|
||||||
int bold, italic, monospace;
|
PyObject *descriptor;
|
||||||
char *cfamily;
|
|
||||||
float point_sz, dpi;
|
float point_sz, dpi;
|
||||||
if(!PyArg_ParseTuple(args, "spppff", &cfamily, &bold, &italic, &monospace, &point_sz, &dpi)) return NULL;
|
if(!PyArg_ParseTuple(args, "Off", &descriptor, &point_sz, &dpi)) return NULL;
|
||||||
NSString *family = [[NSString alloc] initWithCString:cfamily encoding:NSUTF8StringEncoding];
|
|
||||||
if (family == NULL) return PyErr_NoMemory();
|
|
||||||
self = (Face *)type->tp_alloc(type, 0);
|
self = (Face *)type->tp_alloc(type, 0);
|
||||||
if (self) {
|
if (self) {
|
||||||
CTFontSymbolicTraits symbolic_traits = (bold ? kCTFontBoldTrait : 0) | (italic ? kCTFontItalicTrait : 0) | (monospace ? kCTFontMonoSpaceTrait : 0);
|
CTFontDescriptorRef desc = font_descriptor_from_python(descriptor);
|
||||||
NSDictionary *font_traits = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:symbolic_traits] forKey:(NSString *)kCTFontSymbolicTrait];
|
if (desc) {
|
||||||
NSDictionary *font_attributes = [NSDictionary dictionaryWithObjectsAndKeys:family, kCTFontFamilyNameAttribute, font_traits, kCTFontTraitsAttribute, nil];
|
|
||||||
CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithAttributes((CFDictionaryRef)font_attributes);
|
|
||||||
if (descriptor) {
|
|
||||||
self->point_sz = point_sz;
|
self->point_sz = point_sz;
|
||||||
self->scaled_point_sz = (dpi / 72.0) * point_sz;
|
self->scaled_point_sz = (dpi / 72.0) * point_sz;
|
||||||
self->font = CTFontCreateWithFontDescriptor(descriptor, self->scaled_point_sz, NULL);
|
self->font = CTFontCreateWithFontDescriptor(desc, self->scaled_point_sz, NULL);
|
||||||
CFRelease(descriptor);
|
CFRelease(desc);
|
||||||
if (!self->font) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create CTFont object"); }
|
if (!self->font) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create CTFont object"); }
|
||||||
else {
|
else {
|
||||||
self->units_per_em = CTFontGetUnitsPerEm(self->font);
|
self->units_per_em = CTFontGetUnitsPerEm(self->font);
|
||||||
@ -115,17 +138,19 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
|||||||
self->underline_position = CTFontGetUnderlinePosition(self->font);
|
self->underline_position = CTFontGetUnderlinePosition(self->font);
|
||||||
self->underline_thickness = CTFontGetUnderlineThickness(self->font);
|
self->underline_thickness = CTFontGetUnderlineThickness(self->font);
|
||||||
self->scaled_point_sz = CTFontGetSize(self->font);
|
self->scaled_point_sz = CTFontGetSize(self->font);
|
||||||
self->family_name = convert_cfstring(CTFontCopyFamilyName(self->font));
|
self->family_name = convert_cfstring(CTFontCopyFamilyName(self->font), 1);
|
||||||
self->full_name = convert_cfstring(CTFontCopyFullName(self->font));
|
self->full_name = convert_cfstring(CTFontCopyFullName(self->font), 1);
|
||||||
self->postscript_name = convert_cfstring(CTFontCopyPostScriptName(self->font));
|
self->postscript_name = convert_cfstring(CTFontCopyPostScriptName(self->font), 1);
|
||||||
if (self->family_name == NULL || self->full_name == NULL || self->postscript_name == NULL) { Py_CLEAR(self); }
|
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); }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Py_CLEAR(self);
|
Py_CLEAR(self);
|
||||||
PyErr_NoMemory();
|
PyErr_NoMemory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[ family release ];
|
|
||||||
return (PyObject*)self;
|
return (PyObject*)self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +158,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
|||||||
static void
|
static void
|
||||||
dealloc(Face* self) {
|
dealloc(Face* self) {
|
||||||
if (self->font) CFRelease(self->font);
|
if (self->font) CFRelease(self->font);
|
||||||
Py_CLEAR(self->family_name); Py_CLEAR(self->full_name); Py_CLEAR(self->postscript_name);
|
Py_CLEAR(self->family_name); Py_CLEAR(self->full_name); Py_CLEAR(self->postscript_name); Py_CLEAR(self->path);
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,8 +273,8 @@ repr(Face *self) {
|
|||||||
snprintf(buf, sizeof(buf)/sizeof(buf[0]), "ascent=%.1f, descent=%.1f, leading=%.1f, point_sz=%.1f, scaled_point_sz=%.1f, underline_position=%.1f underline_thickness=%.1f",
|
snprintf(buf, sizeof(buf)/sizeof(buf[0]), "ascent=%.1f, descent=%.1f, leading=%.1f, point_sz=%.1f, scaled_point_sz=%.1f, underline_position=%.1f underline_thickness=%.1f",
|
||||||
(self->ascent), (self->descent), (self->leading), (self->point_sz), (self->scaled_point_sz), (self->underline_position), (self->underline_thickness));
|
(self->ascent), (self->descent), (self->leading), (self->point_sz), (self->scaled_point_sz), (self->underline_position), (self->underline_thickness));
|
||||||
return PyUnicode_FromFormat(
|
return PyUnicode_FromFormat(
|
||||||
"Face(family=%U, full_name=%U, postscript_name=%U, units_per_em=%u, %s)",
|
"Face(family=%U, full_name=%U, postscript_name=%U, path=%U, units_per_em=%u, %s)",
|
||||||
self->family_name, self->full_name, self->postscript_name, self->units_per_em, buf
|
self->family_name, self->full_name, self->postscript_name, self->path, self->units_per_em, buf
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,6 +292,7 @@ static PyMemberDef members[] = {
|
|||||||
MEM(underline_position, T_FLOAT),
|
MEM(underline_position, T_FLOAT),
|
||||||
MEM(underline_thickness, T_FLOAT),
|
MEM(underline_thickness, T_FLOAT),
|
||||||
MEM(family_name, T_OBJECT),
|
MEM(family_name, T_OBJECT),
|
||||||
|
MEM(path, T_OBJECT),
|
||||||
MEM(full_name, T_OBJECT),
|
MEM(full_name, T_OBJECT),
|
||||||
MEM(postscript_name, T_OBJECT),
|
MEM(postscript_name, T_OBJECT),
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
|
|||||||
@ -3,18 +3,67 @@
|
|||||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
from kitty.fast_data_types import CTFace as Face
|
import re
|
||||||
from kitty.utils import get_logical_dpi, wcwidth, ceil_int
|
|
||||||
|
from kitty.fast_data_types import CTFace as Face, coretext_all_fonts
|
||||||
|
from kitty.utils import ceil_int, get_logical_dpi, wcwidth
|
||||||
|
|
||||||
main_font = {}
|
main_font = {}
|
||||||
symbol_map = {}
|
symbol_map = {}
|
||||||
cell_width = cell_height = baseline = CellTexture = WideCellTexture = underline_thickness = underline_position = None
|
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'}
|
||||||
|
|
||||||
|
|
||||||
def install_symbol_map(val, font_size, dpi):
|
def create_font_map(all_fonts):
|
||||||
|
ans = {'family_map': {}, 'ps_map': {}, 'full_map': {}}
|
||||||
|
for x in all_fonts:
|
||||||
|
f = (x['family'] or '').lower()
|
||||||
|
s = (x['style'] or '').lower()
|
||||||
|
ps = (x['postscript_name'] or '').lower()
|
||||||
|
ans['family_map'].setdefault(f, []).append(x)
|
||||||
|
ans['ps_map'].setdefault(ps, []).append(x)
|
||||||
|
ans['full_map'].setdefault(f + ' ' + s, []).append(x)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
def find_best_match(font_map, family, bold, italic):
|
||||||
|
q = re.sub(r'\s+', ' ', family.lower())
|
||||||
|
|
||||||
|
def matches(candidate, monospace):
|
||||||
|
return candidate['bold'] == bold and candidate['italic'] == italic and candidate['monospace'] == monospace
|
||||||
|
|
||||||
|
for monospace in (True, False):
|
||||||
|
for selector in 'ps_map full_map family_map'.split():
|
||||||
|
candidates = font_map[selector].get(q, ())
|
||||||
|
if candidates:
|
||||||
|
for candidate in candidates:
|
||||||
|
if matches(candidate, monospace):
|
||||||
|
return candidate
|
||||||
|
if selector != 'family_map':
|
||||||
|
return candidates[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_face(font_map, family, main_family, font_size, dpi, bold=False, italic=False):
|
||||||
|
def resolve_family(f):
|
||||||
|
if f.lower() == 'monospace':
|
||||||
|
f = 'Menlo'
|
||||||
|
if (bold or italic) and f == 'auto':
|
||||||
|
f = main_family
|
||||||
|
return f
|
||||||
|
family = resolve_family(family)
|
||||||
|
descriptor = find_best_match(font_map, family, bold, italic) or {
|
||||||
|
'monospace': True,
|
||||||
|
'bold': bold,
|
||||||
|
'italic': italic,
|
||||||
|
'family': family
|
||||||
|
}
|
||||||
|
return Face(descriptor, font_size, dpi)
|
||||||
|
|
||||||
|
|
||||||
|
def install_symbol_map(all_fonts, val, font_size, dpi):
|
||||||
global symbol_map
|
global symbol_map
|
||||||
symbol_map = {}
|
symbol_map = {}
|
||||||
family_map = {f: Face(f, False, False, False, font_size, dpi) for f in set(val.values())}
|
family_map = {f: get_face(all_fonts, f, 'Menlo', font_size, dpi) for f in set(val.values())}
|
||||||
for ch, family in val.items():
|
for ch, family in val.items():
|
||||||
symbol_map[ch] = family_map[family]
|
symbol_map[ch] = family_map[family]
|
||||||
|
|
||||||
@ -28,7 +77,11 @@ def set_font_family(opts, override_font_size=None, ignore_dpi_failure=False):
|
|||||||
raise
|
raise
|
||||||
dpi = (72, 72) # Happens when running via develop() in an ssh session
|
dpi = (72, 72) # Happens when running via develop() in an ssh session
|
||||||
dpi = sum(dpi) / 2.0
|
dpi = sum(dpi) / 2.0
|
||||||
attr_map = {(False, False): 'font_family', (True, False): 'bold_font', (False, True): 'italic_font', (True, True): 'bold_italic_font'}
|
font_size = override_font_size or opts.font_size
|
||||||
|
all_fonts = create_font_map(coretext_all_fonts())
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
def get_family(bold, italic):
|
def get_family(bold, italic):
|
||||||
ans = getattr(opts, attr_map[(bold, italic)])
|
ans = getattr(opts, attr_map[(bold, italic)])
|
||||||
@ -37,12 +90,8 @@ def set_font_family(opts, override_font_size=None, ignore_dpi_failure=False):
|
|||||||
if ans == 'auto' and (bold or italic):
|
if ans == 'auto' and (bold or italic):
|
||||||
ans = get_family(False, False)
|
ans = get_family(False, False)
|
||||||
return ans
|
return ans
|
||||||
font_size = override_font_size or opts.font_size
|
|
||||||
|
|
||||||
for bold in (False, True):
|
install_symbol_map(all_fonts, opts.symbol_map, font_size, dpi)
|
||||||
for italic in (False, True):
|
|
||||||
main_font[(bold, italic)] = Face(get_family(bold, italic), bold, italic, True, font_size, dpi)
|
|
||||||
install_symbol_map(opts.symbol_map, font_size, dpi)
|
|
||||||
mf = main_font[(False, False)]
|
mf = main_font[(False, False)]
|
||||||
cell_width, cell_height = mf.cell_size()
|
cell_width, cell_height = mf.cell_size()
|
||||||
CellTexture = ctypes.c_ubyte * (cell_width * cell_height)
|
CellTexture = ctypes.c_ubyte * (cell_width * cell_height)
|
||||||
@ -53,6 +102,12 @@ def set_font_family(opts, override_font_size=None, ignore_dpi_failure=False):
|
|||||||
return cell_width, cell_height
|
return cell_width, cell_height
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
def current_cell():
|
def current_cell():
|
||||||
return CellTexture, cell_width, cell_height, baseline, underline_thickness, underline_position
|
return CellTexture, cell_width, cell_height, baseline, underline_thickness, underline_position
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,12 @@
|
|||||||
# font_family Operator Mono Book
|
# font_family Operator Mono Book
|
||||||
# bold_font Operator Mono Thick
|
# bold_font Operator Mono Thick
|
||||||
# bold_italic_font Operator Mono Medium
|
# bold_italic_font Operator Mono Medium
|
||||||
|
# or
|
||||||
|
# font_family SF Mono Medium
|
||||||
|
# bold_font SF Mono Semibold
|
||||||
|
# bold_italic_font SF Mono Semibold
|
||||||
|
# Note that you should use the full family name but do not add Bold or Italic qualifiers
|
||||||
|
# to the name.
|
||||||
font_family monospace
|
font_family monospace
|
||||||
italic_font auto
|
italic_font auto
|
||||||
bold_font auto
|
bold_font auto
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user