From e0e4cff8159b472fcfd18ee84e40f2f89bfee271 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 24 Mar 2021 11:45:11 +0100 Subject: [PATCH] Align glyphs correctly using font-kit's raster_bounds --- gpui/src/fonts.rs | 117 +++++++++++++++----------- gpui/src/platform/mac/geometry.rs | 1 + gpui/src/platform/mac/renderer.rs | 11 +-- gpui/src/platform/mac/sprite_cache.rs | 44 +++++++--- 4 files changed, 104 insertions(+), 69 deletions(-) diff --git a/gpui/src/fonts.rs b/gpui/src/fonts.rs index c1e877b54f..fe0f781c4e 100644 --- a/gpui/src/fonts.rs +++ b/gpui/src/fonts.rs @@ -1,17 +1,20 @@ use crate::geometry::{ rect::RectI, - vector::{vec2f, Vector2F, Vector2I}, + transform2d::Transform2F, + vector::{vec2f, Vector2F}, }; use anyhow::{anyhow, Result}; -use cocoa::appkit::CGPoint; -use core_graphics::{base::CGGlyph, color_space::CGColorSpace, context::CGContext}; -use parking_lot::{RwLock, RwLockUpgradableReadGuard}; - +use cocoa::appkit::{CGFloat, CGPoint}; +use core_graphics::{ + base::CGGlyph, color_space::CGColorSpace, context::CGContext, geometry::CGAffineTransform, +}; pub use font_kit::properties::{Properties, Weight}; use font_kit::{ - font::Font, loaders::core_text::NativeFont, metrics::Metrics, source::SystemSource, + canvas::RasterizationOptions, font::Font, hinting::HintingOptions, + loaders::core_text::NativeFont, metrics::Metrics, source::SystemSource, }; use ordered_float::OrderedFloat; +use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use std::{collections::HashMap, sync::Arc}; #[allow(non_upper_case_globals)] @@ -193,35 +196,51 @@ impl FontCache { font_size: f32, glyph_id: GlyphId, scale_factor: f32, - ) -> Option<(Vector2I, Vec)> { - let native_font = self.native_font(font_id, font_size); - let glyph_id = glyph_id as CGGlyph; - let glyph_bounds = - native_font.get_bounding_rects_for_glyphs(Default::default(), &[glyph_id]); - let position = CGPoint::new(-glyph_bounds.origin.x, -glyph_bounds.origin.y); - let width = (glyph_bounds.size.width * scale_factor as f64).ceil() as usize; - let height = (glyph_bounds.size.height * scale_factor as f64).ceil() as usize; + ) -> Option<(RectI, Vec)> { + let font = self.font(font_id); + let scale = Transform2F::from_scale(scale_factor); + let bounds = font + .raster_bounds( + glyph_id, + font_size, + scale, + HintingOptions::None, + RasterizationOptions::GrayscaleAa, + ) + .ok()?; - if width == 0 || height == 0 { + if bounds.width() == 0 || bounds.height() == 0 { None } else { - let mut ctx = CGContext::create_bitmap_context( - None, - width, - height, + 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 _), + bounds.width() as usize, + bounds.height() as usize, 8, - width, + bounds.width() as usize, &CGColorSpace::create_device_gray(), kCGImageAlphaOnly, ); - ctx.scale(scale_factor as f64, scale_factor as f64); - native_font.draw_glyphs(&[glyph_id], &[position], ctx.clone()); - ctx.flush(); - Some(( - Vector2I::new(width as i32, height as i32), - Vec::from(ctx.data()), - )) + // Move the origin to bottom left and account for scaling, this + // makes drawing text consistent with the font-kit's raster_bounds. + ctx.translate(0.0, bounds.height() as CGFloat); + let transform = scale.translate(-bounds.origin().to_f32()); + ctx.set_text_matrix(&CGAffineTransform { + a: transform.matrix.m11() as CGFloat, + b: -transform.matrix.m21() as CGFloat, + c: -transform.matrix.m12() as CGFloat, + d: transform.matrix.m22() as CGFloat, + tx: transform.vector.x() as CGFloat, + ty: -transform.vector.y() as CGFloat, + }); + + 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)]); + + Some((bounds, pixels)) } } @@ -290,30 +309,28 @@ fn push_font(state: &mut FontCacheState, font: Font) -> FontId { font_id } -#[cfg(test)] -mod tests { - use std::{fs::File, io::BufWriter, path::Path}; +// #[cfg(test)] +// mod tests { +// use std::{fs::File, io::BufWriter, path::Path}; - use super::*; +// 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('m').unwrap(); - let (size, bytes) = cache.render_glyph(font_id, 16.0, glyph_id, 1.).unwrap(); +// #[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 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, size.x() as u32, size.y() 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(); // Save - dbg!(size, bytes); - } -} +// 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(); +// } +// } diff --git a/gpui/src/platform/mac/geometry.rs b/gpui/src/platform/mac/geometry.rs index dc64c48f52..89da409dbd 100644 --- a/gpui/src/platform/mac/geometry.rs +++ b/gpui/src/platform/mac/geometry.rs @@ -1,5 +1,6 @@ use cocoa::foundation::{NSPoint, NSRect, NSSize}; use pathfinder_geometry::{rect::RectF, vector::Vector2F}; + pub trait Vector2FExt { fn to_ns_point(&self) -> NSPoint; fn to_ns_size(&self) -> NSSize; diff --git a/gpui/src/platform/mac/renderer.rs b/gpui/src/platform/mac/renderer.rs index 798f1462f0..c129dca65b 100644 --- a/gpui/src/platform/mac/renderer.rs +++ b/gpui/src/platform/mac/renderer.rs @@ -267,19 +267,20 @@ impl Renderer { let mut sprites_by_atlas = HashMap::new(); for glyph in layer.glyphs() { - if let Some((atlas, bounds)) = self.sprite_cache.render_glyph( + if let Some(sprite) = self.sprite_cache.render_glyph( glyph.font_id, glyph.font_size, glyph.id, scene.scale_factor(), ) { sprites_by_atlas - .entry(atlas) + .entry(sprite.atlas_id) .or_insert_with(Vec::new) .push(shaders::GPUISprite { - origin: (glyph.origin * scene.scale_factor()).to_float2(), - size: bounds.size().to_float2(), - atlas_origin: bounds.origin().to_float2(), + origin: (glyph.origin * scene.scale_factor() + sprite.offset.to_f32()) + .to_float2(), + size: sprite.size.to_float2(), + atlas_origin: sprite.atlas_origin.to_float2(), color: glyph.color.to_uchar4(), }); } diff --git a/gpui/src/platform/mac/sprite_cache.rs b/gpui/src/platform/mac/sprite_cache.rs index 96a72ddebb..650e7429d6 100644 --- a/gpui/src/platform/mac/sprite_cache.rs +++ b/gpui/src/platform/mac/sprite_cache.rs @@ -19,12 +19,20 @@ struct GlyphDescriptor { glyph_id: GlyphId, } +#[derive(Clone)] +pub struct GlyphSprite { + pub atlas_id: usize, + pub atlas_origin: Vector2I, + pub offset: Vector2I, + pub size: Vector2I, +} + pub struct SpriteCache { device: metal::Device, atlas_size: Vector2I, font_cache: Arc, atlasses: Vec, - glyphs: HashMap>, + glyphs: HashMap>, } impl SpriteCache { @@ -49,7 +57,7 @@ impl SpriteCache { font_size: f32, glyph_id: GlyphId, scale_factor: f32, - ) -> Option<(usize, RectI)> { + ) -> Option { let font_cache = &self.font_cache; let atlasses = &mut self.atlasses; let atlas_size = self.atlas_size; @@ -61,20 +69,28 @@ impl SpriteCache { glyph_id, }) .or_insert_with(|| { - let (size, mask) = + let (glyph_bounds, mask) = font_cache.render_glyph(font_id, font_size, glyph_id, scale_factor)?; - assert!(size.x() < atlas_size.x()); - assert!(size.y() < atlas_size.y()); + assert!(glyph_bounds.width() < atlas_size.x()); + assert!(glyph_bounds.height() < atlas_size.y()); - let atlas = atlasses.last_mut().unwrap(); - if let Some(bounds) = atlas.try_insert(size, &mask) { - Some((atlasses.len() - 1, RectI::new(bounds.origin(), size))) - } else { - let mut atlas = Atlas::new(device, atlas_size); - let bounds = atlas.try_insert(size, &mask).unwrap(); - atlasses.push(atlas); - Some((atlasses.len() - 1, RectI::new(bounds.origin(), size))) - } + let atlas_bounds = atlasses + .last_mut() + .unwrap() + .try_insert(glyph_bounds.size(), &mask) + .unwrap_or_else(|| { + let mut atlas = Atlas::new(device, atlas_size); + let bounds = atlas.try_insert(glyph_bounds.size(), &mask).unwrap(); + atlasses.push(atlas); + bounds + }); + + Some(GlyphSprite { + atlas_id: atlasses.len() - 1, + atlas_origin: atlas_bounds.origin(), + offset: glyph_bounds.origin(), + size: glyph_bounds.size(), + }) }) .clone() }