More work on multicell font rendering

This commit is contained in:
Kovid Goyal 2024-12-06 08:42:50 +05:30
parent 8993386399
commit b96da25380
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
4 changed files with 206 additions and 70 deletions

View file

@ -34,6 +34,10 @@ typedef struct {
unsigned int x, y, z, xnum, ynum;
} GPUSpriteTracker;
typedef struct RunFont {
unsigned scale, subscale_n, subscale_d, vertical_align, multicell_y;
ssize_t font_idx;
} RunFont;
static hb_buffer_t *harfbuzz_buffer = NULL;
static hb_feature_t hb_features[3] = {{0}};
@ -75,6 +79,18 @@ static void free_const(const void* x) { free((void*)x); }
#define KEY_DTOR_FN free_const
#include "kitty-verstable.h"
typedef struct ScaledFontData {
FontCellMetrics fcm;
double font_sz_in_pts;
} ScaledFontData;
#define NAME scaled_font_map_t
#define KEY_TY float
#define VAL_TY ScaledFontData
#define HASH_FN vt_hash_float
#define CMPR_FN vt_cmpr_float
#include "kitty-verstable.h"
typedef struct {
FONTS_DATA_HEAD
id_type id;
@ -84,6 +100,7 @@ typedef struct {
Canvas canvas;
GPUSpriteTracker sprite_tracker;
fallback_font_map_t fallback_font_map;
scaled_font_map_t scaled_font_map;
} FontGroup;
static FontGroup* font_groups = NULL;
@ -161,6 +178,7 @@ del_font_group(FontGroup *fg) {
free(fg->canvas.buf); fg->canvas.buf = NULL; fg->canvas = (Canvas){0};
fg->sprite_map = free_sprite_map(fg->sprite_map);
vt_cleanup(&fg->fallback_font_map);
vt_cleanup(&fg->scaled_font_map);
for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i);
free(fg->fonts); fg->fonts = NULL; fg->fonts_count = 0;
}
@ -419,8 +437,8 @@ adjust_ypos(unsigned int pos, unsigned int cell_height, int adjustment) {
}
static void
calc_cell_metrics(FontGroup *fg) {
fg->fcm = cell_metrics(fg->fonts[fg->medium_font_idx].face);
calc_cell_metrics(FontGroup *fg, PyObject *face) {
fg->fcm = cell_metrics(face);
if (!fg->fcm.cell_width) fatal("Failed to calculate cell width for the specified font");
unsigned int before_cell_height = fg->fcm.cell_height;
unsigned int cw = fg->fcm.cell_width, ch = fg->fcm.cell_height;
@ -463,8 +481,6 @@ calc_cell_metrics(FontGroup *fg) {
fg->fcm.baseline += MIN(fg->fcm.cell_height - 1, (unsigned)line_height_adjustment / 2);
fg->fcm.underline_position += MIN(fg->fcm.cell_height - 1, (unsigned)line_height_adjustment / 2);
}
sprite_tracker_set_layout(&fg->sprite_tracker, fg->fcm.cell_width, fg->fcm.cell_height);
ensure_canvas_can_fit(fg, 8, 1);
}
static bool
@ -718,15 +734,58 @@ ensure_glyph_render_scratch_space(size_t sz) {
#undef a
}
static void
scaled_cell_dimensions(RunFont rf, unsigned *width, unsigned *height) {
*width *= rf.scale;
*height *= rf.scale;
static float
effective_scale(RunFont rf) {
float ans = MAX(1u, rf.scale);
if (rf.subscale_n && rf.subscale_d && rf.subscale_n < rf.subscale_d) {
double frac = ((double)rf.subscale_n) / rf.subscale_d;
*width = (unsigned)ceil(frac * *width);
*height = (unsigned)ceil(frac * *height);
ans *= ((float)rf.subscale_n) / rf.subscale_d;
}
return ans;
}
static float
scaled_cell_dimensions(RunFont rf, unsigned *width, unsigned *height) {
float frac = effective_scale(rf);
*width = (unsigned)ceil(frac * *width);
*height = (unsigned)ceil(frac * *height);
return frac;
}
static float
apply_scale_to_font_group(FontGroup *fg, RunFont *rf) {
unsigned int scaled_cell_width = fg->fcm.cell_width, scaled_cell_height = fg->fcm.cell_height;
float scale = rf ? scaled_cell_dimensions(*rf, &scaled_cell_width, &scaled_cell_height) : 1.f;
scaled_font_map_t_itr i = vt_get(&fg->scaled_font_map, scale);
ScaledFontData sfd;
if (vt_is_end(i)) {
Font *medium_font = &fg->fonts[fg->medium_font_idx];
FontGroup copy = {.fcm=fg->fcm, .logical_dpi_x=fg->logical_dpi_x, .logical_dpi_y=fg->logical_dpi_y};
copy.fcm.cell_width = scaled_cell_width; copy.fcm.cell_height = scaled_cell_height;
copy.font_sz_in_pts = scale * fg->font_sz_in_pts;
if (!face_apply_scaling(medium_font->face, (FONTS_DATA_HANDLE)&copy)) {
if (PyErr_Occurred()) PyErr_Print();
fatal("Could not apply scale of %f to font group at size: %f", scale, copy.font_sz_in_pts);
}
calc_cell_metrics(&copy, medium_font->face);
if (copy.fcm.cell_width > scaled_cell_width || copy.fcm.cell_height > scaled_cell_height) {
float wfrac = (float)copy.fcm.cell_width / scaled_cell_width, hfrac = (float)copy.fcm.cell_height / scaled_cell_height;
float frac = MIN(wfrac, hfrac);
copy.font_sz_in_pts *= frac;
while(true) {
if (!face_apply_scaling(medium_font->face, (FONTS_DATA_HANDLE)&copy)) {
if (PyErr_Occurred()) PyErr_Print();
fatal("Could not apply scale of %f to font group at size: %f", scale, copy.font_sz_in_pts);
}
calc_cell_metrics(&copy, medium_font->face);
if (copy.fcm.cell_width <= scaled_cell_width && copy.fcm.cell_height <= scaled_cell_height) break;
if (copy.font_sz_in_pts < 1) fatal("Could not apply scale of %f to font group as font size (%f) is less than minimum threshold", scale, copy.font_sz_in_pts);
copy.font_sz_in_pts -= 0.1;
}
}
} else sfd = i.data->val;
fg->font_sz_in_pts = sfd.font_sz_in_pts;
fg->fcm = sfd.fcm;
return scale;
}
static pixel*
@ -738,6 +797,28 @@ extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells)
return ans;
}
static pixel*
extract_scaled_cell_from_canvas(
Canvas *canvas, unsigned i, unsigned num_cells, Region src, Region dest,
unsigned unscaled_cell_width, unsigned unscaled_cell_height, unsigned scaled_cell_width, unsigned scaled_cell_height
) {
unsigned unscaled_cell_area = unscaled_cell_width * unscaled_cell_height;
pixel *ans = canvas->buf + (canvas->size_in_bytes / sizeof(canvas->buf[0]) - unscaled_cell_area);
memset(ans, 0, sizeof(ans[0]) * unscaled_cell_area);
unsigned xoff = i * unscaled_cell_width;
unsigned src_row_size = scaled_cell_width * num_cells;
if (xoff >= src_row_size) return ans; // nothing to copy
unsigned width = MIN(src_row_size - xoff, unscaled_cell_width);
/*printf("\n");*/
for (unsigned src_y=src.top, dest_y=dest.top; src_y < src.bottom && src_y < scaled_cell_height && dest_y < dest.bottom && dest_y < unscaled_cell_height; src_y++, dest_y++) {
pixel *srcp = canvas->buf + src_row_size * src_y;
pixel *destp = ans + unscaled_cell_width * dest_y;
/*for (unsigned x = xoff; x < xoff + width; x++) { printf("%d ", srcp[x] != 0); } printf("\n");*/
memcpy(destp, srcp + xoff, width * sizeof(destp[0]));
}
return ans;
}
static void
calculate_regions_for_line(RunFont rf, unsigned cell_height, Region *src, Region *dest) {
unsigned src_height = src->bottom;
@ -794,14 +875,15 @@ render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell,
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0));
ensure_canvas_can_fit(fg, 2, rf.scale);
Region src = { .right = width, .bottom = height }, dest = src;
unsigned dest_stride = rf.scale * fg->fcm.cell_width, src_stride = width;
calculate_regions_for_line(rf, fg->fcm.cell_height, &src, &dest);
render_alpha_mask(alpha_mask, fg->canvas.buf, &src, &dest, src_stride, dest_stride, 0xffffff);
render_alpha_mask(alpha_mask, fg->canvas.buf, &src, &dest, width, width, 0xffffff);
/*for (unsigned y = 0; y < height; y++) { for (unsigned x = 0; x < width; x++) { printf("%d ", (fg->canvas.buf + width * y)[0] != 0); } printf("\n"); }*/
if (rf.scale == 1) {
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[0]->x, sp[0]->y, sp[0]->z, fg->canvas.buf);
} else {
calculate_regions_for_line(rf, fg->fcm.cell_height, &src, &dest);
/*printf("width: %u height: %u src.top: %u src.bottom: %u\n", width, height, src.top, src.bottom);*/
for (unsigned i = 0; i < rf.scale; i++) {
pixel *b = extract_cell_from_canvas(fg, i, rf.scale);
pixel *b = extract_scaled_cell_from_canvas(&fg->canvas, i, 1, src, dest, fg->fcm.cell_width, fg->fcm.cell_height, width, height);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->x, sp[i]->y, sp[i]->z, b);
}
}
@ -835,7 +917,11 @@ set_cell_sprite(GPUCell *cell, const SpritePosition *sp) {
}
static void
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, RunFont rf, glyph_index *glyphs, unsigned glyph_count, bool center_glyph, const TextCache *tc) {
render_group(
FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells,
hb_glyph_info_t *info, hb_glyph_position_t *positions, RunFont rf, glyph_index *glyphs, unsigned glyph_count,
bool center_glyph, const TextCache *tc, float scale, FontCellMetrics unscaled_metrics
) {
#define sp global_glyph_render_scratch.sprite_positions
int error = 0;
bool all_rendered = true;
@ -862,14 +948,26 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas.buf, fg->fcm.cell_width, fg->fcm.cell_height, num_cells, fg->fcm.baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph);
if (PyErr_Occurred()) PyErr_Print();
for (unsigned i = 0; i < num_cells; i++) {
if (!sp[i]->rendered) {
sp[i]->rendered = true;
sp[i]->colored = was_colored;
pixel *buf = num_cells == 1 ? fg->canvas.buf : extract_cell_from_canvas(fg, i, num_cells);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->x, sp[i]->y, sp[i]->z, buf);
if (scale == 1.f) {
for (unsigned i = 0; i < num_cells; i++) {
if (!sp[i]->rendered) {
sp[i]->rendered = true;
sp[i]->colored = was_colored;
pixel *buf = num_cells == 1 ? fg->canvas.buf : extract_cell_from_canvas(fg, i, num_cells);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->x, sp[i]->y, sp[i]->z, buf);
}
set_cell_sprite(gpu_cells + i, sp[i]);
}
} else {
Region src={.bottom=fg->fcm.cell_height, .right=fg->fcm.cell_width * num_cells}, dest={0};
calculate_regions_for_line(rf, unscaled_metrics.cell_height, &src, &dest);
for (unsigned i = 0; i < num_cells; i++) {
if (!sp[i]->rendered) {
pixel *b = extract_scaled_cell_from_canvas(&fg->canvas, i, num_cells, src, dest, unscaled_metrics.cell_width, unscaled_metrics.cell_height, fg->fcm.cell_width, fg->fcm.cell_height);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->x, sp[i]->y, sp[i]->z, b);
}
set_cell_sprite(gpu_cells + i, sp[i]);
}
set_cell_sprite(gpu_cells + i, sp[i]);
}
#undef sp
}
@ -1269,8 +1367,9 @@ group_normal(Font *font, hb_font_t *hbf, const TextCache *tc) {
static void
shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font, RunFont rf, bool disable_ligature, const TextCache *tc) {
face_apply_scaling(font->face, rf);
shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font, RunFont rf, FontGroup *fg, bool disable_ligature, const TextCache *tc) {
float scale = apply_scale_to_font_group(fg, &rf);
if (scale != 1.f) if (!face_apply_scaling(font->face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print();
hb_font_t *hbf = harfbuzz_font_for_face(font->face);
if (font->spacer_strategy == SPACER_STRATEGY_UNKNOWN) detect_spacer_strategy(hbf, font, tc);
shape(first_cpu_cell, first_gpu_cell, num_cells, hbf, font, disable_ligature, tc);
@ -1282,6 +1381,10 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, group_state.num_glyphs, dbuf, sizeof(dbuf), NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS);
printf("\n%s\n", dbuf);
#endif
if (scale != 1.f) {
apply_scale_to_font_group(fg, NULL);
if (!face_apply_scaling(font->face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print();
}
}
static void
@ -1321,6 +1424,9 @@ split_run_at_offset(index_type cursor_offset, index_type *left, index_type *righ
static void
render_groups(FontGroup *fg, RunFont rf, bool center_glyph, const TextCache *tc) {
unsigned idx = 0;
FontCellMetrics unscaled_metrics = fg->fcm;
float scale = apply_scale_to_font_group(fg, &rf);
if (scale != 1.f) if (!face_apply_scaling(fg->fonts[rf.font_idx].face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print();
while (idx <= G(group_idx)) {
Group *group = G(groups) + idx;
if (!group->num_cells) break;
@ -1329,10 +1435,15 @@ render_groups(FontGroup *fg, RunFont rf, bool center_glyph, const TextCache *tc)
if (group->num_glyphs) {
ensure_glyph_render_scratch_space(MAX(group->num_glyphs, group->num_cells));
for (unsigned i = 0; i < group->num_glyphs; i++) global_glyph_render_scratch.glyphs[i] = G(info)[group->first_glyph_idx + i].codepoint;
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, rf, global_glyph_render_scratch.glyphs, group->num_glyphs, center_glyph, tc);
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, rf, global_glyph_render_scratch.glyphs, group->num_glyphs, center_glyph, tc, scale, unscaled_metrics);
}
idx++;
}
if (scale != 1.f) {
apply_scale_to_font_group(fg, NULL);
if (!face_apply_scaling(fg->fonts[rf.font_idx].face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print();
}
}
static PyObject*
@ -1353,6 +1464,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
PyObject *face = NULL;
Font *font;
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create at least one font group first"); return NULL; }
FontGroup *fg = font_groups;
if (path) {
face = face_from_path(path, index, (FONTS_DATA_HANDLE)font_groups);
if (face == NULL) return NULL;
@ -1360,11 +1472,10 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
font->face = face;
if (!init_hash_tables(font)) return NULL;
} else {
FontGroup *fg = font_groups;
font = fg->fonts + fg->medium_font_idx;
}
RunFont rf = {0};
shape_run(line->cpu_cells, line->gpu_cells, num, font, rf, false, line->text_cache);
shape_run(line->cpu_cells, line->gpu_cells, num, font, rf, fg, false, line->text_cache);
PyObject *ans = PyList_New(0);
unsigned int idx = 0;
@ -1388,20 +1499,20 @@ static void
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, RunFont rf, bool pua_space_ligature, bool center_glyph, int cursor_offset, DisableLigature disable_ligature_strategy, const TextCache *tc) {
switch(rf.font_idx) {
default:
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[rf.font_idx], rf, disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS, tc);
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[rf.font_idx], rf, fg, disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS, tc);
if (pua_space_ligature) collapse_pua_space_ligature(num_cells);
else if (cursor_offset > -1) { // false if DISABLE_LIGATURES_NEVER
index_type left, right;
split_run_at_offset(cursor_offset, &left, &right);
if (right > left) {
if (left) {
shape_run(first_cpu_cell, first_gpu_cell, left, &fg->fonts[rf.font_idx], rf, false, tc);
shape_run(first_cpu_cell, first_gpu_cell, left, &fg->fonts[rf.font_idx], rf, fg, false, tc);
render_groups(fg, rf, center_glyph, tc);
}
shape_run(first_cpu_cell + left, first_gpu_cell + left, right - left, &fg->fonts[rf.font_idx], rf, true, tc);
shape_run(first_cpu_cell + left, first_gpu_cell + left, right - left, &fg->fonts[rf.font_idx], rf, fg, true, tc);
render_groups(fg, rf, center_glyph, tc);
if (right < num_cells) {
shape_run(first_cpu_cell + right, first_gpu_cell + right, num_cells - right, &fg->fonts[rf.font_idx], rf, false, tc);
shape_run(first_cpu_cell + right, first_gpu_cell + right, num_cells - right, &fg->fonts[rf.font_idx], rf, fg, false, tc);
render_groups(fg, rf, center_glyph, tc);
}
break;
@ -1636,6 +1747,7 @@ initialize_font_group(FontGroup *fg) {
fg->fonts_count = 1; // the 0 index font is the box font
if (!init_hash_tables(fg->fonts)) fatal("Out of memory");
vt_init(&fg->fallback_font_map);
vt_init(&fg->scaled_font_map);
#define I(attr) if (descriptor_indices.attr) fg->attr##_font_idx = initialize_font(fg, descriptor_indices.attr, #attr); else fg->attr##_font_idx = -1;
fg->medium_font_idx = initialize_font(fg, 0, "medium");
I(bold); I(italic); I(bi);
@ -1647,12 +1759,16 @@ initialize_font_group(FontGroup *fg) {
fg->first_fallback_font_idx++;
}
#undef I
calc_cell_metrics(fg);
calc_cell_metrics(fg, fg->fonts[fg->medium_font_idx].face);
ensure_canvas_can_fit(fg, 8, 1);
sprite_tracker_set_layout(&fg->sprite_tracker, fg->fcm.cell_width, fg->fcm.cell_height);
// rescale the symbol_map faces for the desired cell height, this is how fallback fonts are sized as well
for (size_t i = 0; i < descriptor_indices.num_symbol_fonts; i++) {
Font *font = fg->fonts + i + fg->first_symbol_font_idx;
set_size_for_face(font->face, fg->fcm.cell_height, true, (FONTS_DATA_HANDLE)fg);
}
ScaledFontData sfd = {.fcm=fg->fcm, .font_sz_in_pts=fg->font_sz_in_pts};
vt_insert(&fg->scaled_font_map, 1.f, sfd);
}

View file

@ -31,12 +31,6 @@ typedef struct ParsedFontFeature {
bool hash_computed;
} ParsedFontFeature;
typedef struct RunFont {
unsigned scale, subscale_n, subscale_d, vertical_align, multicell_y;
ssize_t font_idx;
} RunFont;
ParsedFontFeature* parse_font_feature(const char *spec);
// API that font backends need to implement
@ -62,7 +56,7 @@ void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len);
typedef void (*free_extra_data_func)(void*);
StringCanvas render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline);
StringCanvas render_simple_text(FONTS_DATA_HANDLE fg_, const char *text);
static inline void face_apply_scaling(PyObject*face, RunFont rf) {(void)face; (void)rf;}
bool face_apply_scaling(PyObject*face, const FONTS_DATA_HANDLE fg);
bool
add_font_name_record(PyObject *table, uint16_t platform_id, uint16_t encoding_id, uint16_t language_id, uint16_t name_id, const char *string, uint16_t string_len);

View file

@ -30,15 +30,20 @@ typedef union FaceIndex {
FT_Long val;
} FaceIndex;
typedef struct FaceMetrics {
float size_in_pts;
unsigned int units_per_EM;
// The following are in font units use font_units_to_pixels_x/y to convert to pixels
int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness;
} FaceMetrics;
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;
FaceMetrics metrics;
int hinting, hintstyle;
bool is_scalable, has_color, is_variable, has_svg;
float size_in_pts;
FT_F26Dot6 char_width, char_height;
FT_UInt xdpi, ydpi;
PyObject *path;
@ -46,7 +51,6 @@ typedef struct {
hb_codepoint_t space_glyph_id;
void *extra_data;
free_extra_data_func free_extra_data;
float apple_leading;
PyObject *name_lookup_table;
FontFeatures font_features;
} Face;
@ -128,7 +132,7 @@ get_height_for_char(Face *self, char ch) {
unsigned int ans = 0;
int glyph_index = FT_Get_Char_Index(self->face, ch);
if (load_glyph(self, glyph_index, FT_LOAD_DEFAULT)) {
unsigned int baseline = font_units_to_pixels_y(self, self->ascender);
unsigned int baseline = font_units_to_pixels_y(self, self->metrics.ascender);
FT_GlyphSlotRec *glyph = self->face->glyph;
FT_Bitmap *bm = &glyph->bitmap;
if (glyph->bitmap_top <= 0 || (glyph->bitmap_top > 0 && (unsigned int)glyph->bitmap_top < baseline)) {
@ -140,7 +144,7 @@ get_height_for_char(Face *self, char ch) {
static unsigned int
calc_cell_height(Face *self, bool for_metrics) {
unsigned int ans = font_units_to_pixels_y(self, self->height);
unsigned int ans = font_units_to_pixels_y(self, self->metrics.height);
if (for_metrics) {
unsigned int underscore_height = get_height_for_char(self, '_');
if (underscore_height > ans) {
@ -193,7 +197,7 @@ set_size_for_face(PyObject *s, unsigned int desired_height, bool force, FONTS_DA
FT_F26Dot6 w = (FT_F26Dot6)(ceil(fg->font_sz_in_pts * 64.0));
FT_UInt xdpi = (FT_UInt)fg->logical_dpi_x, ydpi = (FT_UInt)fg->logical_dpi_y;
if (!force && (self->char_width == w && self->char_height == w && self->xdpi == xdpi && self->ydpi == ydpi)) return true;
((Face*)self)->size_in_pts = (float)fg->font_sz_in_pts;
self->metrics.size_in_pts = (float)fg->font_sz_in_pts;
return set_font_size(self, w, w, xdpi, ydpi, desired_height, fg->fcm.cell_height);
}
@ -204,16 +208,32 @@ set_size(Face *self, PyObject *args) {
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;
self->metrics.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;
static void
copy_face_metrics(Face *self) {
#define CPY(n) self->metrics.n = self->face->n;
CPY(units_per_EM); CPY(ascender); CPY(descender); CPY(height); CPY(max_advance_width); CPY(max_advance_height); CPY(underline_position); CPY(underline_thickness);
#undef CPY
}
bool
face_apply_scaling(PyObject *f, const FONTS_DATA_HANDLE fg) {
Face *self = (Face*)f;
if (set_size_for_face(f, 0, false, fg)) {
if (self->harfbuzz_font) hb_ft_font_changed(self->harfbuzz_font);
copy_face_metrics(self);
return true;
}
return false;
}
static bool
init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_HANDLE fg) {
copy_face_metrics(self);
self->is_scalable = FT_IS_SCALABLE(self->face);
self->has_color = FT_HAS_COLOR(self->face);
self->is_variable = FT_HAS_MULTIPLE_MASTERS(self->face);
@ -230,8 +250,8 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_
TT_OS2 *os2 = (TT_OS2*)FT_Get_Sfnt_Table(self->face, FT_SFNT_OS2);
if (os2 != NULL) {
self->strikethrough_position = os2->yStrikeoutPosition;
self->strikethrough_thickness = os2->yStrikeoutSize;
self->metrics.strikethrough_position = os2->yStrikeoutPosition;
self->metrics.strikethrough_thickness = os2->yStrikeoutSize;
}
self->path = path; Py_INCREF(self->path);
@ -397,17 +417,17 @@ cell_metrics(PyObject *s) {
FontCellMetrics ans = {0};
ans.cell_width = calc_cell_width(self);
ans.cell_height = calc_cell_height(self, true);
ans.baseline = font_units_to_pixels_y(self, self->ascender);
ans.underline_position = MIN(ans.cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->ascender - self->underline_position)));
ans.underline_thickness = MAX(1, font_units_to_pixels_y(self, self->underline_thickness));
ans.baseline = font_units_to_pixels_y(self, self->metrics.ascender);
ans.underline_position = MIN(ans.cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->metrics.ascender - self->metrics.underline_position)));
ans.underline_thickness = MAX(1, font_units_to_pixels_y(self, self->metrics.underline_thickness));
if (self->strikethrough_position != 0) {
ans.strikethrough_position = MIN(ans.cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->ascender - self->strikethrough_position)));
if (self->metrics.strikethrough_position != 0) {
ans.strikethrough_position = MIN(ans.cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->metrics.ascender - self->metrics.strikethrough_position)));
} else {
ans.strikethrough_position = (unsigned int)floor(ans.baseline * 0.65);
}
if (self->strikethrough_thickness > 0) {
ans.strikethrough_thickness = MAX(1, font_units_to_pixels_y(self, self->strikethrough_thickness));
if (self->metrics.strikethrough_thickness > 0) {
ans.strikethrough_thickness = MAX(1, font_units_to_pixels_y(self, self->metrics.strikethrough_thickness));
} else {
ans.strikethrough_thickness = ans.underline_thickness;
}
@ -1037,22 +1057,26 @@ end:
static PyMemberDef members[] = {
#define MEM(name, type) {#name, type, offsetof(Face, name), READONLY, #name}
MEM(units_per_EM, T_UINT),
MEM(ascender, T_INT),
MEM(descender, T_INT),
MEM(height, T_INT),
MEM(max_advance_width, T_INT),
MEM(max_advance_height, T_INT),
MEM(underline_position, T_INT),
MEM(underline_thickness, T_INT),
MEM(strikethrough_position, T_INT),
MEM(strikethrough_thickness, T_INT),
#define MMEM(name, type) {#name, type, offsetof(Face, metrics) + offsetof(FaceMetrics, name), READONLY, #name}
MMEM(units_per_EM, T_UINT),
MMEM(size_in_pts, T_FLOAT),
MMEM(ascender, T_INT),
MMEM(descender, T_INT),
MMEM(height, T_INT),
MMEM(max_advance_width, T_INT),
MMEM(max_advance_height, T_INT),
MMEM(underline_position, T_INT),
MMEM(underline_thickness, T_INT),
MMEM(strikethrough_position, T_INT),
MMEM(strikethrough_thickness, T_INT),
MEM(is_scalable, T_BOOL),
MEM(is_variable, T_BOOL),
MEM(has_svg, T_BOOL),
MEM(has_color, T_BOOL),
MEM(path, T_OBJECT_EX),
{NULL} /* Sentinel */
#undef MEM
#undef MMEM
};
static PyMethodDef methods[] = {

View file

@ -18,6 +18,8 @@ vt_hash_bytes(const void *data, const size_t size) {
return hash;
}
static inline uint64_t vt_hash_float(float x) { return vt_hash_integer((uint64_t)x); }
static inline bool vt_cmpr_float(float a, float b) { return a == b; }
#define vt_create_for_loop(itr_type, itr, table) for (itr_type itr = vt_first(table); !vt_is_end(itr); itr = vt_next(itr))
#endif