More work on positioning glyphs with CoreText
This commit is contained in:
parent
0999ce159e
commit
c86c166448
@ -281,8 +281,19 @@ specialize_font_descriptor(PyObject *base_descriptor) {
|
|||||||
return base_descriptor;
|
return base_descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint8_t *render_buf = NULL;
|
||||||
|
static size_t render_buf_sz = 0;
|
||||||
|
static CGGlyph glyphs[128];
|
||||||
|
static CGRect boxes[128];
|
||||||
|
|
||||||
|
static void
|
||||||
|
finalize(void) {
|
||||||
|
free(render_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
coretext_render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int width, unsigned int height, unsigned int baseline) {
|
render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int width, unsigned int height, unsigned int baseline) {
|
||||||
CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
|
CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
|
||||||
if (color_space == NULL) fatal("Out of memory");
|
if (color_space == NULL) fatal("Out of memory");
|
||||||
CGContextRef ctx = CGBitmapContextCreate(buf, width, height, 8, 4 * width, color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault);
|
CGContextRef ctx = CGBitmapContextCreate(buf, width, height, 8, 4 * width, color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault);
|
||||||
@ -293,12 +304,10 @@ coretext_render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned
|
|||||||
CGAffineTransform transform = CGAffineTransformIdentity;
|
CGAffineTransform transform = CGAffineTransformIdentity;
|
||||||
CGContextSetTextDrawingMode(ctx, kCGTextFill);
|
CGContextSetTextDrawingMode(ctx, kCGTextFill);
|
||||||
CGGlyph glyph = glyph_id;
|
CGGlyph glyph = glyph_id;
|
||||||
// TODO: Scale the glyph if its bbox is larger than the image by using a non-identity transform
|
|
||||||
/* CGRect rect = CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationHorizontal, glyphs, 0, 1); */
|
|
||||||
CGContextSetTextMatrix(ctx, transform);
|
CGContextSetTextMatrix(ctx, transform);
|
||||||
CGFloat pos_y = height - 1.2f * baseline; // we want the emoji to be rendered a little below the baseline
|
CGContextSetTextPosition(ctx, -boxes[0].origin.x, MAX(2, height - 1.2f * baseline)); // lower the emoji a bit so its bottom is not on the baseline
|
||||||
CGContextSetTextPosition(ctx, 0, MAX(2, pos_y));
|
CGPoint p = CGPointMake(0, 0);
|
||||||
CTFontDrawGlyphs(font, &glyph, &CGPointZero, 1, ctx);
|
CTFontDrawGlyphs(font, &glyph, &p, 1, ctx);
|
||||||
CGContextRelease(ctx);
|
CGContextRelease(ctx);
|
||||||
CGColorSpaceRelease(color_space);
|
CGColorSpaceRelease(color_space);
|
||||||
for (size_t r = 0; r < width; r++) {
|
for (size_t r = 0; r < width; r++) {
|
||||||
@ -309,15 +318,6 @@ coretext_render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static uint8_t *render_buf = NULL;
|
|
||||||
static size_t render_buf_sz = 0;
|
|
||||||
|
|
||||||
static void
|
|
||||||
finalize(void) {
|
|
||||||
free(render_buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
ensure_render_space(size_t width, size_t height) {
|
ensure_render_space(size_t width, size_t height) {
|
||||||
if (render_buf_sz >= width * height) return;
|
if (render_buf_sz >= width * height) return;
|
||||||
@ -328,45 +328,55 @@ ensure_render_space(size_t width, size_t height) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
render_glyph(CTFontRef font, CGGlyph glyph, unsigned int width, unsigned int height, unsigned int y_origin, CGFloat x, CGFloat y) {
|
render_glyph(CTFontRef font, CGGlyph glyph, unsigned int width, unsigned int height, unsigned int baseline, unsigned int i) {
|
||||||
memset(render_buf, 0, render_buf_sz);
|
memset(render_buf, 0, render_buf_sz);
|
||||||
CGColorSpaceRef gray_color_space = CGColorSpaceCreateDeviceGray();
|
CGColorSpaceRef gray_color_space = CGColorSpaceCreateDeviceGray();
|
||||||
CGContextRef render_ctx = CGBitmapContextCreate(render_buf, width, height, 8, width, gray_color_space, (kCGBitmapAlphaInfoMask & kCGImageAlphaNone));
|
CGContextRef render_ctx = CGBitmapContextCreate(render_buf, width, height, 8, width, gray_color_space, (kCGBitmapAlphaInfoMask & kCGImageAlphaNone));
|
||||||
if (render_ctx == NULL || gray_color_space == NULL) fatal("Out of memory");
|
if (render_ctx == NULL || gray_color_space == NULL) fatal("Out of memory");
|
||||||
CGContextSetShouldAntialias(render_ctx, true);
|
CGContextSetShouldAntialias(render_ctx, true);
|
||||||
CGContextSetShouldSmoothFonts(render_ctx, true); // sub-pixel antialias
|
CGContextSetShouldSmoothFonts(render_ctx, true);
|
||||||
CGContextSetRGBFillColor(render_ctx, 1, 1, 1, 1); // white glyphs
|
CGContextSetGrayFillColor(render_ctx, 1, 1); // white glyphs
|
||||||
CGContextSetTextDrawingMode(render_ctx, kCGTextFill);
|
CGContextSetTextDrawingMode(render_ctx, kCGTextFill);
|
||||||
CGContextSetTextMatrix(render_ctx, CGAffineTransformIdentity);
|
CGContextSetTextMatrix(render_ctx, CGAffineTransformIdentity);
|
||||||
CGContextSetTextPosition(render_ctx, 0, y_origin);
|
CGContextSetTextPosition(render_ctx, MAX(0, -boxes[i].origin.x), height - baseline);
|
||||||
CGPoint p = CGPointMake(x, y);
|
CGPoint p = CGPointMake(0, 0);
|
||||||
CTFontDrawGlyphs(font, &glyph, &p, 1, render_ctx);
|
CTFontDrawGlyphs(font, &glyph, &p, 1, render_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
place_glyph_in_canvas(float x, pixel *canvas, unsigned int canvas_width, unsigned int cell_height) {
|
place_glyph_in_canvas(unsigned int i, float x_offset, float y_offset, pixel *canvas, unsigned int canvas_width, unsigned int cell_height) {
|
||||||
Region src = {.bottom=cell_height, .right=canvas_width}, dest = {.left=(unsigned int)ceil(x), .right=canvas_width, .bottom=cell_height};
|
Region src = {.bottom=cell_height, .right=canvas_width}, dest = {.bottom=cell_height, .right=canvas_width};
|
||||||
|
// Calculate column bounds
|
||||||
|
float bearing_x = boxes[i].origin.x;
|
||||||
|
(void)bearing_x;
|
||||||
|
(void)x_offset;
|
||||||
|
(void)y_offset;
|
||||||
|
|
||||||
render_alpha_mask(render_buf, canvas, &src, &dest, canvas_width, canvas_width);
|
render_alpha_mask(render_buf, canvas, &src, &dest, canvas_width, canvas_width);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
render_glyphs_in_cells(PyObject *s, bool UNUSED bold, bool UNUSED italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored) {
|
render_glyphs_in_cells(PyObject *s, bool UNUSED bold, bool UNUSED italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored) {
|
||||||
CTFace *self = (CTFace*)s;
|
CTFace *self = (CTFace*)s;
|
||||||
printf("was_colored: %d num_glyphs: %u\n", *was_colored, num_glyphs);
|
|
||||||
if (*was_colored) {
|
|
||||||
coretext_render_color_glyph(self->ct_font, (uint8_t*)canvas, info[0].codepoint, cell_width * num_cells, cell_height, baseline);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
unsigned int canvas_width = cell_width * num_cells;
|
unsigned int canvas_width = cell_width * num_cells;
|
||||||
ensure_render_space(canvas_width, cell_height);
|
for (unsigned i=0; i < num_glyphs; i++) glyphs[i] = info[i].codepoint;
|
||||||
|
CGRect br = CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationHorizontal, glyphs, boxes, num_glyphs);
|
||||||
float x = 0.f, y_offset = 0.f, x_offset = 0.f;
|
float x = 0.f, y_offset = 0.f, x_offset = 0.f;
|
||||||
|
if (*was_colored) {
|
||||||
|
render_color_glyph(self->ct_font, (uint8_t*)canvas, info[0].codepoint, cell_width * num_cells, cell_height, baseline);
|
||||||
|
} else {
|
||||||
|
ensure_render_space(canvas_width, cell_height);
|
||||||
for (unsigned int i = 0; i < num_glyphs; i++) {
|
for (unsigned int i = 0; i < num_glyphs; i++) {
|
||||||
x_offset = (float)positions[i].x_offset / 64.0f;
|
render_glyph(self->ct_font, info[i].codepoint, canvas_width, cell_height, baseline, i);
|
||||||
y_offset = (float)positions[i].y_offset / 64.0f;
|
place_glyph_in_canvas(i, x + x_offset, y_offset, canvas, canvas_width, cell_height);
|
||||||
render_glyph(self->ct_font, info[i].codepoint, canvas_width, cell_height, cell_height - baseline, x_offset, y_offset);
|
|
||||||
place_glyph_in_canvas(x, canvas, canvas_width, cell_height);
|
|
||||||
x += (float)positions[i].x_advance / 64.0f;
|
x += (float)positions[i].x_advance / 64.0f;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (num_cells > 1) {
|
||||||
|
// center glyphs
|
||||||
|
CGFloat delta = canvas_width - br.size.width;
|
||||||
|
if (delta > 1) right_shift_canvas(canvas, canvas_width, cell_height, (unsigned)(delta / 2.f));
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,3 +32,13 @@ void sprite_tracker_set_layout(unsigned int cell_width, unsigned int cell_height
|
|||||||
typedef void (*free_extra_data_func)(void*);
|
typedef void (*free_extra_data_func)(void*);
|
||||||
PyObject* ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float);
|
PyObject* ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float);
|
||||||
PyObject* ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float);
|
PyObject* ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float);
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) {
|
||||||
|
pixel *src;
|
||||||
|
size_t r;
|
||||||
|
for (r = 0, src = canvas; r < height; r++, src += width) {
|
||||||
|
memmove(src + amt, src, sizeof(pixel) * (width - amt));
|
||||||
|
memset(src, 0, sizeof(pixel) * amt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -264,5 +264,5 @@ def showcase():
|
|||||||
f = 'monospace' if is_macos else 'Liberation Mono'
|
f = 'monospace' if is_macos else 'Liberation Mono'
|
||||||
test_render_string('He\u0347\u0305llo\u0337, w\u0302or\u0306l\u0354d!', family=f)
|
test_render_string('He\u0347\u0305llo\u0337, w\u0302or\u0306l\u0354d!', family=f)
|
||||||
test_render_string('你好,世界', family=f)
|
test_render_string('你好,世界', family=f)
|
||||||
test_render_string('|😁|🙏|😺|', family=f)
|
test_render_string('│😁│🙏│😺│', family=f)
|
||||||
test_render_string('A=>>B!=C', family='Fira Code')
|
test_render_string('A=>>B!=C', family='Fira Code')
|
||||||
|
|||||||
@ -482,16 +482,6 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
|
|||||||
} 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
|
||||||
right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) {
|
|
||||||
pixel *src;
|
|
||||||
size_t r;
|
|
||||||
for (r = 0, src = canvas; r < height; r++, src += width) {
|
|
||||||
memmove(src + amt, src, sizeof(pixel) * (width - amt));
|
|
||||||
memset(src, 0, sizeof(pixel) * amt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const ProcessedBitmap EMPTY_PBM = {.factor = 1};
|
static const ProcessedBitmap EMPTY_PBM = {.factor = 1};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user