Compare commits

..

15 Commits

Author SHA1 Message Date
Kovid Goyal
7f61f1f9f3 version 0.34.1 2024-04-19 11:05:41 +05:30
Kovid Goyal
bb3b5df99a better field name 2024-04-18 22:46:20 +05:30
Kovid Goyal
ab26d2204f Update changelog 2024-04-18 22:36:07 +05:30
Kovid Goyal
2e2caa167a Better fix for #7263
Don't blank the upper 16 bits when copying index from freetype face
2024-04-18 22:36:00 +05:30
Kovid Goyal
4ba6d02ab6 Revert "Workaround for fontconfig returning junk in all but the lowest eight bits for FC_INDEX"
This reverts commit d2c21ee297.

Apparently fontconfig overloads the top 16 bits as "instance index"
which is some kind of index for variable fonts? Who knows. fontconfig is
a natural disaster.

Fixes #7361
2024-04-18 22:06:57 +05:30
Kovid Goyal
b9dc48b798 ... 2024-04-18 11:50:30 +05:30
Kovid Goyal
37f842d06a Merge branch 'build-docs-typo-fix' of https://github.com/timstapl/kitty 2024-04-17 20:28:19 +05:30
Tim Stapleton
2cdabdfebd fix typo in dependency name 2024-04-17 09:51:31 -05:00
Kovid Goyal
63ffa2606c Give the close X a distressed look 2024-04-17 11:05:05 +05:30
Kovid Goyal
f66ee68834 Wayland GNOME: Draw the titlebar buttons without using a font
Fixes #7349
2024-04-17 10:39:30 +05:30
Kovid Goyal
494892c7e4 Dont create the region unless actually needed 2024-04-16 18:16:28 +05:30
Kovid Goyal
6c1a83ffd7 Wayland KDE: Fix window background blur not adapting when window is grown. Also fix turning it on and off not working.
Fixes #7351
2024-04-16 18:11:26 +05:30
Kovid Goyal
e531791f79 Code to get peer pid on a few more platforms 2024-04-15 19:14:29 +05:30
Kovid Goyal
5b587060a4 Fix #7346 2024-04-15 16:07:38 +05:30
Kovid Goyal
4ad8d30751 Prevent a crash when user uses notifications in a kitty process run via a binary from outside a bundle 2024-04-15 11:59:25 +05:30
11 changed files with 253 additions and 56 deletions

View File

@@ -116,7 +116,7 @@ Build-time dependencies:
- ``libssl-dev``
- ``libpython3-dev``
- ``libxxhash-dev``
- ``libsmide-dev``
- ``libsimde-dev``
Build and run from source with Nix

View File

@@ -50,6 +50,15 @@ consumption to do the same tasks.
Detailed list of changes
-------------------------------------
0.34.1 [2024-04-19]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Wayland KDE: Fix window background blur not adapting when window is grown. Also fix turning it on and off not working. (:iss:`7351`)
- Wayland GNOME: Draw the titlebar buttons without using a font (:iss:`7349`)
- Fix a regression in the previous release that caused incorrect font selection when using variable fonts on Linux (:iss:`7361`)
0.34.0 [2024-04-15]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -465,9 +465,9 @@ for tmux refusing to support images.
If you use any of the advanced features that kitty has innovated, such as
:doc:`styled underlines </underlines>`, :doc:`desktop notifications
</desktop-notifications>`, :doc:`extended keyboard support
</keyboard-protocol>`, :doc:`file transfer </kittens/transfer>`, etc.
they may or may not work, depending on the whims of tmux's maintainer,
your version of tmux, etc.
</keyboard-protocol>`, :doc:`file transfer </kittens/transfer>`, :doc:`the ssh
kitten </kittens/ssh>`, etc. they may or may not work,
depending on the whims of tmux's maintainer, your version of tmux, etc.
I opened and closed a lot of windows/tabs and top shows kitty's memory usage is very high?

View File

@@ -171,6 +171,158 @@ csd_initialize_metrics(_GLFWwindow *window) {
decs.metrics.vertical = decs.metrics.width + decs.metrics.top;
}
static void
patch_titlebar_with_alpha_mask(uint32_t *dest, uint8_t *src, unsigned height, unsigned dest_stride, unsigned src_width, unsigned dest_left, uint32_t bg, uint32_t fg) {
for (unsigned y = 0; y < height; y++, src += src_width, dest += dest_stride) {
uint32_t *d = dest + dest_left;
for (unsigned i = 0; i < src_width; i++) {
const uint8_t alpha = src[i], calpha = 255 - alpha;
// Blend the red and blue components
uint32_t ans = ((bg & 0xff00ff) * calpha + (fg & 0xff00ff) * alpha) & 0xff00ff00;
// Blend the green component
ans += ((bg & 0xff00) * calpha + (fg & 0xff00) * alpha) & 0xff0000;
ans >>= 8;
d[i] = ans | 0xff000000;
}
}
}
static void
render_hline(uint8_t *out, unsigned width, unsigned thickness, unsigned bottom, unsigned left, unsigned right) {
for (unsigned y = bottom - thickness; y < bottom; y++) {
uint8_t *dest = out + width * y;
for (unsigned x = left; x < right; x++) dest[x] = 255;
}
}
static void
render_vline(uint8_t *out, unsigned width, unsigned thickness, unsigned left, unsigned top, unsigned bottom) {
for (unsigned y = top; y < bottom; y++) {
uint8_t *dest = out + width * y;
for (unsigned x = left; x < left + thickness; x++) dest[x] = 255;
}
}
static int
scale(unsigned thickness, float factor) {
return (unsigned)(roundf(thickness * factor));
}
static void
render_minimize(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, width * height);
unsigned thickness = height / 12;
unsigned baseline = height - thickness * 2;
unsigned side_margin = (int)roundf(thickness * 3.8f);
if (!thickness || width <= side_margin || height < baseline + 2 * thickness) return;
render_hline(out, width, thickness, baseline, side_margin, width - side_margin);
}
static void
render_maximize(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, width * height);
unsigned thickness = height / 12, half_thickness = thickness / 2;
unsigned baseline = height - thickness * 2;
unsigned side_margin = scale(thickness, 3.0f);
unsigned top = 4 * thickness;
if (!half_thickness || width <= side_margin || height < baseline + 2 * thickness || top >= baseline) return;
render_hline(out, width, half_thickness, baseline, side_margin, width - side_margin);
render_hline(out, width, thickness, top + thickness, side_margin, width - side_margin);
render_vline(out, width, half_thickness, side_margin, top, baseline);
render_vline(out, width, half_thickness, width - side_margin, top, baseline);
}
static void
render_restore(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, width * height);
unsigned thickness = height / 12, half_thickness = thickness / 2;
unsigned baseline = height - thickness * 2;
unsigned side_margin = scale(thickness, 3.0f);
unsigned top = 4 * thickness;
if (!half_thickness || width <= side_margin || height < baseline + 2 * thickness || top >= baseline) return;
unsigned box_height = ((baseline - top) * 3) / 4;
if (box_height < 2*thickness) return;
unsigned box_width = ((width - 2 * side_margin) * 3) / 4;
// bottom box
unsigned box_top = baseline - box_height, left = side_margin, right = side_margin + box_width, bottom = baseline;
render_hline(out, width, thickness, box_top + thickness, left, right);
render_hline(out, width, half_thickness, bottom, left, right);
render_vline(out, width, half_thickness, left, box_top, bottom);
render_vline(out, width, half_thickness, side_margin + box_width, baseline - box_height, baseline);
// top box
unsigned box_x_shift = 2 * thickness, box_y_shift = 2 * thickness;
box_x_shift = MIN(width - right, box_x_shift);
box_y_shift = MIN(box_top, box_y_shift);
unsigned left2 = left + box_x_shift, right2 = right + box_x_shift, top2 = box_top - box_y_shift, bottom2 = bottom - box_y_shift;
render_hline(out, width, thickness, top2 + thickness, left2, right2);
render_vline(out, width, half_thickness, right2, top2, bottom2);
render_hline(out, width, half_thickness, bottom2, right, right2);
render_vline(out, width, half_thickness, left2, top2, box_top);
}
static void
render_line(uint8_t *buf, unsigned width, unsigned height, unsigned thickness, int x1, int y1, int x2, int y2) {
float m = (y2 - y1) / (float)(x2 - x1);
float c = y1 - m * x1;
unsigned delta = thickness / 2, extra = thickness % 2;
for (int x = MAX(0, MIN(x1, x2)); x < MIN((int)width, MAX(x1, x2) + 1); x++) {
float ly = m * x + c;
for (int y = MAX(0, (int)(ly - delta)); y < MIN((int)height, (int)(ly + delta + extra + 1)); y++) buf[x + y * width] = 255;
}
for (int y = MAX(0, MIN(y1, y2)); y < MIN((int)height, MAX(y1, y2) + 1); y++) {
float lx = (y - c) / m;
for (int x = MAX(0, (int)(lx - delta)); x < MIN((int)width, (int)(lx + delta + extra + 1)); x++) buf[x + y * width] = 255;
}
}
static void
render_close(uint8_t *out, unsigned width, unsigned height) {
memset(out, 0, width * height);
unsigned thickness = height / 12;
unsigned baseline = height - thickness * 2;
unsigned side_margin = scale(thickness, 3.3f);
int top = baseline - (width - 2 * side_margin);
if (top <= 0) return;
unsigned line_thickness = scale(thickness, 1.5f);
render_line(out, width, height, line_thickness, side_margin, top, width - side_margin, baseline);
render_line(out, width, height, line_thickness, side_margin, baseline, width - side_margin, top);
}
static uint32_t
average_intensity_in_src(uint8_t *src, unsigned src_width, unsigned src_x, unsigned src_y, unsigned factor) {
uint32_t ans = 0;
for (unsigned y = src_y; y < src_y + factor; y++) {
uint8_t *s = src + src_width * y;
for (unsigned x = src_x; x < src_x + factor; x++) ans += s[x];
}
return ans / (factor * factor);
}
static void
downsample(uint8_t *dest, uint8_t *src, unsigned dest_width, unsigned dest_height, unsigned factor) {
unsigned src_width = factor * dest_width;
for (unsigned y = 0; y < dest_height; y++) {
uint8_t *d = dest + dest_width * y;
for (unsigned x = 0; x < dest_width; x++) {
d[x] = MIN(255u, (uint32_t)d[x] + average_intensity_in_src(src, src_width, x * factor, y * factor, factor));
}
}
}
static void
render_button(void(*which)(uint8_t *, unsigned, unsigned), bool antialias, uint32_t *dest, uint8_t *src, unsigned height, unsigned dest_stride, unsigned src_width, unsigned dest_left, uint32_t bg, uint32_t fg) {
if (antialias) {
static const unsigned factor = 4;
uint8_t *big_src = malloc(factor * factor * height * src_width);
if (big_src) {
which(big_src, src_width * factor, height * factor);
memset(src, 0, src_width * height);
downsample(src, big_src, src_width, height, factor);
free(big_src);
} else which(src, src_width, height);
} else which(src, src_width, height);
patch_titlebar_with_alpha_mask(dest, src, height, dest_stride, src_width, dest_left, bg, fg);
}
static void
render_title_bar(_GLFWwindow *window, bool to_front_buffer) {
@@ -194,7 +346,7 @@ render_title_bar(_GLFWwindow *window, bool to_front_buffer) {
uint8_t *output = to_front_buffer ? decs.titlebar.buffer.data.front : decs.titlebar.buffer.data.back;
// render text part
int button_size = (int)roundf(decs.metrics.visible_titlebar_height * decs.for_window_state.fscale);
int button_size = decs.titlebar.buffer.height;
int num_buttons = 1;
if (window->wl.wm_capabilities.maximize) num_buttons++;
if (window->wl.wm_capabilities.minimize) num_buttons++;
@@ -207,16 +359,20 @@ render_title_bar(_GLFWwindow *window, bool to_front_buffer) {
render_buttons:
decs.maximize.width = 0; decs.minimize.width = 0; decs.close.width = 0;
if (!button_size) return;
uint8_t *alpha_mask = malloc(button_size * button_size);
int left = decs.titlebar.buffer.width - num_buttons * button_size;
#define draw(which, text, hover_bg) { \
_glfw.callbacks.draw_text((GLFWwindow*)window, text, fg_color, decs.which.hovered ? hover_bg : bg_color, output, decs.titlebar.buffer.width, \
decs.titlebar.buffer.height, /*x=*/left, /*y=*/0, /*right_margin=*/decs.titlebar.buffer.width - left - button_size, true); \
decs.which.left = left; decs.which.width = button_size; left += button_size; \
}
if (window->wl.wm_capabilities.minimize) draw(minimize, "🗕", hover_bg);
if (window->wl.wm_capabilities.maximize) draw(maximize, is_maximized ? "🗗" : "🗖", hover_bg);
draw(close, "🗙", is_dark ? 0xff880000: 0xffc80000);
#undef draw
if (!alpha_mask || left <= 0) return;
#define drawb(which, antialias, func, hover_bg) { \
render_button(func, antialias, (uint32_t*)output, alpha_mask, button_size, decs.titlebar.buffer.width, button_size, left, decs.which.hovered ? hover_bg : bg_color, fg_color); decs.which.left = left; decs.which.width = button_size; left += button_size; }
if (window->wl.wm_capabilities.minimize) drawb(minimize, false, render_minimize, hover_bg);
if (window->wl.wm_capabilities.maximize) {
if (is_maximized) { drawb(maximize, false, render_restore, hover_bg); } else { drawb(maximize, false, render_maximize, hover_bg); }
}
drawb(close, true, render_close, is_dark ? 0xff880000: 0xffc80000);
free(alpha_mask);
#undef drawb
}
static void

28
glfw/wl_init.c vendored
View File

@@ -633,17 +633,31 @@ GLFWAPI GLFWColorScheme glfwGetCurrentSystemColorTheme(void) {
return glfw_current_system_color_theme();
}
static pid_t
get_socket_peer_pid(int fd) {
#ifdef __linux__
struct ucred ucred;
socklen_t len = sizeof(struct ucred);
return (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) ? -1 : ucred.pid;
#elif defined(LOCAL_PEERCRED) && defined(XUCRED_VERSION)
struct xucred peercred;
socklen_t peercredlen = sizeof(peercred);
return (getsockopt(c->fd, LOCAL_PEERCRED, 1, (void *)&peercred, &peercredlen) == 0 && peercred.cr_version == XUCRED_VERSION) ? peercred.cr_pid : -1;
#elif defined(LOCAL_PEERPID)
pid_t pid;
socklen_t pid_size = sizeof(pid);
return getsockopt(client, SOL_LOCAL, LOCAL_PEERPID, &pid, &pid_size) == -1 ? -1 : pid;
#else
errno = ENOSYS;
return -1;
#endif
}
GLFWAPI pid_t glfwWaylandCompositorPID(void) {
if (!_glfw.wl.display) return -1;
int fd = wl_display_get_fd(_glfw.wl.display);
if (fd < 0) return -1;
struct ucred ucred;
socklen_t len = sizeof(struct ucred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) {
perror("getsockopt failed");
return -1;
}
return ucred.pid;
return get_socket_peer_pid(fd);
}
//////////////////////////////////////////////////////////////////////////

34
glfw/wl_window.c vendored
View File

@@ -316,20 +316,30 @@ checkScaleChange(_GLFWwindow* window) {
static void
update_regions(_GLFWwindow* window) {
if (window->wl.transparent && !window->wl.org_kde_kwin_blur) return;
struct wl_region* region = wl_compositor_create_region(_glfw.wl.compositor);
if (!region) return;
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
// Makes the surface considered as XRGB instead of ARGB.
if (!window->wl.transparent) wl_surface_set_opaque_region(window->wl.surface, region);
if (!window->wl.transparent) {
struct wl_region* region = wl_compositor_create_region(_glfw.wl.compositor);
if (!region) return;
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
// Makes the surface considered as XRGB instead of ARGB.
wl_surface_set_opaque_region(window->wl.surface, region);
wl_region_destroy(region);
}
// Set blur region
if (window->wl.org_kde_kwin_blur) {
org_kde_kwin_blur_set_region(window->wl.org_kde_kwin_blur, window->wl.has_blur ? region: NULL);
org_kde_kwin_blur_commit(window->wl.org_kde_kwin_blur);
if (_glfw.wl.org_kde_kwin_blur_manager) {
if (window->wl.has_blur) {
if (!window->wl.org_kde_kwin_blur)
window->wl.org_kde_kwin_blur = org_kde_kwin_blur_manager_create(_glfw.wl.org_kde_kwin_blur_manager, window->wl.surface);
if (window->wl.org_kde_kwin_blur) {
// NULL means entire window
org_kde_kwin_blur_set_region(window->wl.org_kde_kwin_blur, NULL);
org_kde_kwin_blur_commit(window->wl.org_kde_kwin_blur);
}
} else {
org_kde_kwin_blur_manager_unset(_glfw.wl.org_kde_kwin_blur_manager, window->wl.surface);
if (window->wl.org_kde_kwin_blur) { org_kde_kwin_blur_release(window->wl.org_kde_kwin_blur); window->wl.org_kde_kwin_blur = NULL; }
}
}
wl_region_destroy(region);
}
int
@@ -2618,8 +2628,6 @@ _glfwPlatformSetWindowBlur(_GLFWwindow *window, int blur_radius) {
bool has_blur = window->wl.has_blur;
bool new_has_blur = blur_radius > 0;
if (new_has_blur != has_blur) {
if (!window->wl.org_kde_kwin_blur)
window->wl.org_kde_kwin_blur = org_kde_kwin_blur_manager_create(_glfw.wl.org_kde_kwin_blur_manager, window->wl.surface);
window->wl.has_blur = new_has_blur;
update_regions(window);
}

View File

@@ -438,9 +438,12 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
}
@end
static void
schedule_notification(const char *identifier, const char *title, const char *body, const char *subtitle) {
static UNUserNotificationCenter*
get_notification_center_safely(void) {
NSBundle *b = [NSBundle mainBundle];
// when bundleIdentifier is nil currentNotificationCenter crashes instead
// of returning nil. Apple...purveyor of shiny TOYS
if (!b || !b.bundleIdentifier) return nil;
UNUserNotificationCenter *center = nil;
@try {
center = [UNUserNotificationCenter currentNotificationCenter];
@@ -448,6 +451,12 @@ schedule_notification(const char *identifier, const char *title, const char *bod
log_error("Failed to get current UNUserNotificationCenter object with error: %s (%s)",
[[e name] UTF8String], [[e reason] UTF8String]);
}
return center;
}
static void
schedule_notification(const char *identifier, const char *title, const char *body, const char *subtitle) {
UNUserNotificationCenter *center = get_notification_center_safely();
if (!center) return;
// Configure the notification's payload.
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
@@ -509,13 +518,7 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
char *identifier = NULL, *title = NULL, *body = NULL, *subtitle = NULL;
if (!PyArg_ParseTuple(args, "zsz|z", &identifier, &title, &body, &subtitle)) return NULL;
UNUserNotificationCenter *center = nil;
@try {
center = [UNUserNotificationCenter currentNotificationCenter];
} @catch (NSException *e) {
log_error("Failed to get current UNUserNotificationCenter object with error: %s (%s)",
[[e name] UTF8String], [[e reason] UTF8String]);
}
UNUserNotificationCenter *center = get_notification_center_safely();
if (!center) Py_RETURN_NONE;
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
queue_notification(identifier, title, body, subtitle);

View File

@@ -22,7 +22,7 @@ class Version(NamedTuple):
appname: str = 'kitty'
kitty_face = '🐱'
version: Version = Version(0, 34, 0)
version: Version = Version(0, 34, 1)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat

View File

@@ -147,8 +147,6 @@ pyspacing(int val) {
#undef S
}
static PyObject*
pyindex(long x) { return PyLong_FromLong(x & 0xffff); }
static PyObject*
pattern_as_dict(FcPattern *pat) {
@@ -192,7 +190,7 @@ pattern_as_dict(FcPattern *pat) {
I(FC_WIDTH, width)
I(FC_SLANT, slant);
I(FC_HINT_STYLE, hint_style);
G(int, FcPatternGetInteger, FC_INDEX, pyindex, index);
I(FC_INDEX, index);
I(FC_RGBA, subpixel);
I(FC_LCD_FILTER, lcdfilter);
B(FC_HINTING, hinting);
@@ -313,7 +311,6 @@ _native_fc_match(FcPattern *pat, FontConfigFace *ans) {
#undef g
ans->path = strdup((char*)out);
if (!ans->path) { PyErr_NoMemory(); goto end; }
ans->index &= 0xffff;
ok = true;
end:
if (match != NULL) FcPatternDestroy(match);
@@ -406,7 +403,7 @@ specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE fg) {
if (pat == NULL) return PyErr_NoMemory();
long face_idx = MAX(0, PyLong_AsLong(idx));
AP(FcPatternAddString, FC_FILE, (const FcChar8*)PyUnicode_AsUTF8(p), "path");
AP(FcPatternAddInteger, FC_INDEX, face_idx & 0xffff, "index");
AP(FcPatternAddInteger, FC_INDEX, face_idx, "index");
AP(FcPatternAddDouble, FC_SIZE, fg->font_sz_in_pts, "size");
AP(FcPatternAddDouble, FC_DPI, (fg->logical_dpi_x + fg->logical_dpi_y) / 2.0, "dpi");
ans = _fc_match(pat);

View File

@@ -144,7 +144,7 @@ def cross(buf: BufType, width: int, height: int, a: int = 1, b: int = 1, c: int
def downsample(src: BufType, dest: BufType, dest_width: int, dest_height: int, factor: int = 4) -> None:
src_width = 4 * dest_width
src_width = factor * dest_width
def average_intensity_in_src(dest_x: int, dest_y: int) -> int:
src_y = dest_y * factor

View File

@@ -19,13 +19,23 @@
#include FT_BITMAP_H
#include FT_TRUETYPE_TABLES_H
typedef union FaceIndex {
struct {
FT_Long ttc_index : 16;
FT_Long variation_index : 16;
};
FT_Long val;
} FaceIndex;
typedef struct {
PyObject_HEAD
FT_Face face;
unsigned int units_per_EM;
int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness;
int hinting, hintstyle, index;
int hinting, hintstyle;
FaceIndex instance;
bool is_scalable, has_color;
float size_in_pts;
FT_F26Dot6 char_width, char_height;
@@ -205,7 +215,7 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_
self->path = path;
Py_INCREF(self->path);
self->index = self->face->face_index & 0xFFFF;
self->instance.val = self->face->face_index;
self->space_glyph_id = glyph_id_for_codepoint((PyObject*)self, ' ');
return true;
}
@@ -225,7 +235,7 @@ face_equals_descriptor(PyObject *face_, PyObject *descriptor) {
if (!t) return false;
if (PyObject_RichCompareBool(face->path, t, Py_EQ) != 1) return false;
t = PyDict_GetItemString(descriptor, "index");
if (t && PyLong_AsLong(t) != face->index) return false;
if (t && PyLong_AsLong(t) != face->instance.val) return false;
return true;
}
@@ -288,10 +298,10 @@ static PyObject *
repr(Face *self) {
const char *ps_name = FT_Get_Postscript_Name(self->face);
return PyUnicode_FromFormat(
"Face(family=%s, style=%s, ps_name=%s, path=%S, index=%d, is_scalable=%S, has_color=%S, ascender=%i, descender=%i, height=%i, underline_position=%i, underline_thickness=%i, strikethrough_position=%i, strikethrough_thickness=%i)",
"Face(family=%s, style=%s, ps_name=%s, path=%S, ttc_index=%d, variation_index=0x%x is_scalable=%S, has_color=%S, ascender=%i, descender=%i, height=%i, underline_position=%i, underline_thickness=%i, strikethrough_position=%i, strikethrough_thickness=%i)",
self->face->family_name ? self->face->family_name : "", self->face->style_name ? self->face->style_name : "",
ps_name ? ps_name: "",
self->path, self->index, self->is_scalable ? Py_True : Py_False, self->has_color ? Py_True : Py_False,
self->path, self->instance.ttc_index, self->instance.variation_index, self->is_scalable ? Py_True : Py_False, self->has_color ? Py_True : Py_False,
self->ascender, self->descender, self->height, self->underline_position, self->underline_thickness, self->strikethrough_position, self->strikethrough_thickness
);
}