diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index b56e94695..0e79f9033 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -390,13 +390,28 @@ pub const Face = struct { // and copy the atlas. const bitmap_original = bitmap_converted orelse bitmap_ft; const bitmap_resized: ?freetype.c.struct_FT_Bitmap_ = resized: { - const max = metrics.cell_height; - const bm = bitmap_original; - if (bm.rows <= max) break :resized null; + const original_width = bitmap_original.width; + const original_height = bitmap_original.rows; + var result = bitmap_original; + // TODO: We are limiting this to only emoji. We can rework this after a future + // improvement (promised by Qwerasd) which implements more flexible resizing rules. For + // now, this will suffice + if (self.isColorGlyph(glyph_index) and opts.cell_width != null) { + const cell_width = opts.cell_width orelse unreachable; + // If we have a cell_width, we constrain the glyph to fit within the cell + result.width = metrics.cell_width * @as(u32, cell_width); + result.rows = (result.width * original_height) / original_width; + } else { + // If we don't have a cell_width, we scale to fill vertically + result.rows = metrics.cell_height; + result.width = (metrics.cell_height * original_width) / original_height; + } + + // If we already fit, we don't need to resize + if (original_height <= result.rows and original_width <= result.width) { + break :resized null; + } - var result = bm; - result.rows = max; - result.width = (result.rows * bm.width) / bm.rows; result.pitch = @as(c_int, @intCast(result.width)) * atlas.format.depth(); const buf = try alloc.alloc( @@ -407,10 +422,10 @@ pub const Face = struct { errdefer alloc.free(buf); if (stb.stbir_resize_uint8( - bm.buffer, - @intCast(bm.width), - @intCast(bm.rows), - bm.pitch, + bitmap_original.buffer, + @intCast(original_width), + @intCast(original_height), + bitmap_original.pitch, result.buffer, @intCast(result.width), @intCast(result.rows), @@ -520,7 +535,7 @@ pub const Face = struct { // NOTE(mitchellh): I don't know if this is right, this doesn't // _feel_ right, but it makes all my limited test cases work. if (self.face.hasColor() and !self.face.isScalable()) { - break :offset_y @intCast(tgt_h); + break :offset_y @intCast(tgt_h + (metrics.cell_height -| tgt_h) / 2); } // The Y offset is the offset of the top of our bitmap PLUS our diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 3e674c715..20a0a82b3 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -1392,29 +1392,29 @@ pub fn rebuildCells( // Try to read the cells from the shaping cache if we can. self.font_shaper_cache.get(run) orelse cache: { - // Otherwise we have to shape them. - const cells = try self.font_shaper.shape(run); + // Otherwise we have to shape them. + const cells = try self.font_shaper.shape(run); - // Try to cache them. If caching fails for any reason we - // continue because it is just a performance optimization, - // not a correctness issue. - self.font_shaper_cache.put( - self.alloc, - run, - cells, - ) catch |err| { - log.warn( - "error caching font shaping results err={}", - .{err}, - ); + // Try to cache them. If caching fails for any reason we + // continue because it is just a performance optimization, + // not a correctness issue. + self.font_shaper_cache.put( + self.alloc, + run, + cells, + ) catch |err| { + log.warn( + "error caching font shaping results err={}", + .{err}, + ); + }; + + // The cells we get from direct shaping are always owned + // by the shaper and valid until the next shaping call so + // we can safely use them. + break :cache cells; }; - // The cells we get from direct shaping are always owned - // by the shaper and valid until the next shaping call so - // we can safely use them. - break :cache cells; - }; - // Advance our index until we reach or pass // our current x position in the shaper cells. while (shaper_cells.?[shaper_cells_i].x < x) { @@ -1637,29 +1637,29 @@ pub fn rebuildCells( // Try to read the cells from the shaping cache if we can. self.font_shaper_cache.get(run) orelse cache: { - // Otherwise we have to shape them. - const cells = try self.font_shaper.shape(run); + // Otherwise we have to shape them. + const cells = try self.font_shaper.shape(run); - // Try to cache them. If caching fails for any reason we - // continue because it is just a performance optimization, - // not a correctness issue. - self.font_shaper_cache.put( - self.alloc, - run, - cells, - ) catch |err| { - log.warn( - "error caching font shaping results err={}", - .{err}, - ); + // Try to cache them. If caching fails for any reason we + // continue because it is just a performance optimization, + // not a correctness issue. + self.font_shaper_cache.put( + self.alloc, + run, + cells, + ) catch |err| { + log.warn( + "error caching font shaping results err={}", + .{err}, + ); + }; + + // The cells we get from direct shaping are always owned + // by the shaper and valid until the next shaping call so + // we can safely use them. + break :cache cells; }; - // The cells we get from direct shaping are always owned - // by the shaper and valid until the next shaping call so - // we can safely use them. - break :cache cells; - }; - const cells = shaper_cells orelse break :glyphs; // If there are no shaper cells for this run, ignore it. @@ -2105,6 +2105,7 @@ fn addGlyph( shaper_run.font_index, shaper_cell.glyph_index, .{ + .cell_width = if (cell.wide == .wide) 2 else 1, .grid_metrics = self.grid_metrics, .thicken = self.config.font_thicken, .thicken_strength = self.config.font_thicken_strength,