Render different variants according to subpixel positioning

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

gpui/Cargo.toml                       |  4 +
gpui/src/fonts.rs                     | 26 ---------------
gpui/src/platform/mac/fonts.rs        | 48 +++++++++++++++++++++++++---
gpui/src/platform/mac/renderer.rs     |  4 +-
gpui/src/platform/mac/sprite_cache.rs | 26 ++++++++++++---
gpui/src/platform/mod.rs              |  1 
6 files changed, 69 insertions(+), 40 deletions(-)

Detailed changes

gpui/Cargo.toml 🔗

@@ -14,7 +14,6 @@ parking_lot = "0.11.1"
 pathfinder_color = "0.5"
 pathfinder_geometry = "0.5"
 pin-project = "1.0.5"
-png = "0.16"
 rand = "0.8.3"
 replace_with = "0.1.7"
 smallvec = "1.6.1"
@@ -25,6 +24,9 @@ tree-sitter = "0.17"
 bindgen = "0.57"
 cc = "1.0.67"
 
+[dev-dependencies]
+png = "0.16"
+
 [target.'cfg(target_os = "macos")'.dependencies]
 anyhow = "1"
 cocoa = "0.24"

gpui/src/fonts.rs 🔗

@@ -158,29 +158,3 @@ impl FontCache {
         metric * font_size / self.metric(font_id, |m| m.units_per_em as f32)
     }
 }
-
-// #[cfg(test)]
-// mod tests {
-//     use std::{fs::File, io::BufWriter, path::Path};
-
-//     use super::*;
-
-//     #[test]
-//     fn test_render_glyph() {
-//         let cache = FontCache::new();
-//         let family_id = cache.load_family(&["Fira Code"]).unwrap();
-//         let font_id = cache.select_font(family_id, &Default::default()).unwrap();
-//         let glyph_id = cache.font(font_id).glyph_for_char('G').unwrap();
-//         let (bounds, bytes) = cache.render_glyph(font_id, 16.0, glyph_id, 1.).unwrap();
-
-//         let path = Path::new(r"/Users/as-cii/Desktop/image.png");
-//         let file = File::create(path).unwrap();
-//         let ref mut w = BufWriter::new(file);
-
-//         let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
-//         encoder.set_color(png::ColorType::Grayscale);
-//         encoder.set_depth(png::BitDepth::Eight);
-//         let mut writer = encoder.write_header().unwrap();
-//         writer.write_image_data(&bytes).unwrap();
-//     }
-// }

gpui/src/platform/mac/fonts.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     geometry::{
         rect::{RectF, RectI},
         transform2d::Transform2F,
-        vector::vec2f,
+        vector::{vec2f, vec2i},
     },
     platform,
     text_layout::{Glyph, Line, Run},
@@ -18,9 +18,7 @@ use core_foundation::{
 use core_graphics::{
     base::CGGlyph, color_space::CGColorSpace, context::CGContext, geometry::CGAffineTransform,
 };
-use core_text::{
-    font_descriptor::kCTFontSizeAttribute, line::CTLine, string_attributes::kCTFontAttributeName,
-};
+use core_text::{line::CTLine, string_attributes::kCTFontAttributeName};
 use font_kit::{
     canvas::RasterizationOptions, hinting::HintingOptions, metrics::Metrics,
     properties::Properties, source::SystemSource,
@@ -73,11 +71,12 @@ impl platform::FontSystem for FontSystem {
         font_id: FontId,
         font_size: f32,
         glyph_id: GlyphId,
+        horizontal_shift: f32,
         scale_factor: f32,
     ) -> Option<(RectI, Vec<u8>)> {
         self.0
             .read()
-            .rasterize_glyph(font_id, font_size, glyph_id, scale_factor)
+            .rasterize_glyph(font_id, font_size, glyph_id, horizontal_shift, scale_factor)
     }
 
     fn layout_str(
@@ -127,6 +126,7 @@ impl FontSystemState {
         font_id: FontId,
         font_size: f32,
         glyph_id: GlyphId,
+        horizontal_shift: f32,
         scale_factor: f32,
     ) -> Option<(RectI, Vec<u8>)> {
         let font = &self.fonts[font_id.0];
@@ -144,6 +144,8 @@ impl FontSystemState {
         if bounds.width() == 0 || bounds.height() == 0 {
             None
         } else {
+            // Make room for subpixel variants.
+            let bounds = RectI::new(bounds.origin(), bounds.size() + vec2i(1, 0));
             let mut pixels = vec![0; bounds.width() as usize * bounds.height() as usize];
             let ctx = CGContext::create_bitmap_context(
                 Some(pixels.as_mut_ptr() as *mut _),
@@ -170,7 +172,13 @@ impl FontSystemState {
 
             ctx.set_font(&font.native_font().copy_to_CGFont());
             ctx.set_font_size(font_size as CGFloat);
-            ctx.show_glyphs_at_positions(&[glyph_id as CGGlyph], &[CGPoint::new(0.0, 0.0)]);
+            ctx.show_glyphs_at_positions(
+                &[glyph_id as CGGlyph],
+                &[CGPoint::new(
+                    (horizontal_shift / scale_factor) as CGFloat,
+                    0.0,
+                )],
+            );
 
             Some((bounds, pixels))
         }
@@ -335,4 +343,32 @@ mod tests {
         );
         Ok(())
     }
+
+    // #[test]
+    // fn test_render_glyph() {
+    //     use std::{fs::File, io::BufWriter, path::Path};
+
+    //     let fonts = FontSystem::new();
+    //     let font_ids = fonts.load_family("Fira Code").unwrap();
+    //     let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
+    //     let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
+
+    //     for i in 0..2 {
+    //         let variant = 0.5 * i as f32;
+    //         let (bounds, bytes) = fonts
+    //             .rasterize_glyph(font_id, 16.0, glyph_id, variant, 2.)
+    //             .unwrap();
+
+    //         let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
+    //         let path = Path::new(&name);
+    //         let file = File::create(path).unwrap();
+    //         let ref mut w = BufWriter::new(file);
+
+    //         let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
+    //         encoder.set_color(png::ColorType::Grayscale);
+    //         encoder.set_depth(png::BitDepth::Eight);
+    //         let mut writer = encoder.write_header().unwrap();
+    //         writer.write_image_data(&bytes).unwrap();
+    //     }
+    // }
 }

gpui/src/platform/mac/renderer.rs 🔗

@@ -272,14 +272,14 @@ impl Renderer {
                 glyph.font_id,
                 glyph.font_size,
                 glyph.id,
+                glyph.origin.x(),
                 scene.scale_factor(),
             ) {
                 sprites_by_atlas
                     .entry(sprite.atlas_id)
                     .or_insert_with(Vec::new)
                     .push(shaders::GPUISprite {
-                        origin: (glyph.origin * scene.scale_factor() + sprite.offset.to_f32())
-                            .to_float2(),
+                        origin: (glyph.origin * scene.scale_factor() + sprite.offset).to_float2(),
                         size: sprite.size.to_float2(),
                         atlas_origin: sprite.atlas_origin.to_float2(),
                         color: glyph.color.to_uchar4(),

gpui/src/platform/mac/sprite_cache.rs 🔗

@@ -2,7 +2,7 @@ use crate::{
     fonts::{FontId, GlyphId},
     geometry::{
         rect::RectI,
-        vector::{vec2i, Vector2I},
+        vector::{vec2i, Vector2F, Vector2I},
     },
     platform,
 };
@@ -16,13 +16,14 @@ struct GlyphDescriptor {
     font_id: FontId,
     font_size: OrderedFloat<f32>,
     glyph_id: GlyphId,
+    subpixel_variant: u8,
 }
 
 #[derive(Clone)]
 pub struct GlyphSprite {
     pub atlas_id: usize,
     pub atlas_origin: Vector2I,
-    pub offset: Vector2I,
+    pub offset: Vector2F,
     pub size: Vector2I,
 }
 
@@ -59,21 +60,34 @@ impl SpriteCache {
         font_id: FontId,
         font_size: f32,
         glyph_id: GlyphId,
+        target_x: f32,
         scale_factor: f32,
     ) -> Option<GlyphSprite> {
+        const SUBPIXEL_VARIANTS: u8 = 4;
+
+        let target_x = target_x * scale_factor;
         let fonts = &self.fonts;
         let atlasses = &mut self.atlasses;
         let atlas_size = self.atlas_size;
         let device = &self.device;
+        let subpixel_variant =
+            (target_x.fract() * SUBPIXEL_VARIANTS as f32).round() as u8 % SUBPIXEL_VARIANTS;
         self.glyphs
             .entry(GlyphDescriptor {
                 font_id,
                 font_size: OrderedFloat(font_size),
                 glyph_id,
+                subpixel_variant,
             })
             .or_insert_with(|| {
-                let (glyph_bounds, mask) =
-                    fonts.rasterize_glyph(font_id, font_size, glyph_id, scale_factor)?;
+                let horizontal_shift = subpixel_variant as f32 / SUBPIXEL_VARIANTS as f32;
+                let (glyph_bounds, mask) = fonts.rasterize_glyph(
+                    font_id,
+                    font_size,
+                    glyph_id,
+                    horizontal_shift,
+                    scale_factor,
+                )?;
                 assert!(glyph_bounds.width() < atlas_size.x());
                 assert!(glyph_bounds.height() < atlas_size.y());
 
@@ -88,10 +102,12 @@ impl SpriteCache {
                         bounds
                     });
 
+                let mut offset = glyph_bounds.origin().to_f32();
+                offset.set_x(offset.x() - target_x.fract());
                 Some(GlyphSprite {
                     atlas_id: atlasses.len() - 1,
                     atlas_origin: atlas_bounds.origin(),
-                    offset: glyph_bounds.origin(),
+                    offset,
                     size: glyph_bounds.size(),
                 })
             })

gpui/src/platform/mod.rs 🔗

@@ -78,6 +78,7 @@ pub trait FontSystem: Send + Sync {
         font_id: FontId,
         font_size: f32,
         glyph_id: GlyphId,
+        horizontal_shift: f32,
         scale_factor: f32,
     ) -> Option<(RectI, Vec<u8>)>;
     fn layout_str(&self, text: &str, font_size: f32, runs: &[(Range<usize>, FontId)]) -> Line;