diff --git a/gpui/src/platform.rs b/gpui/src/platform.rs index ad8a705034cc758f9ecda11e596ce0cd369d3a27..7159e0c14969f72cf58f8c6877a7c348158aeccd 100644 --- a/gpui/src/platform.rs +++ b/gpui/src/platform.rs @@ -136,5 +136,5 @@ pub trait FontSystem: Send + Sync { font_size: f32, runs: &[(usize, FontId, ColorU)], ) -> LineLayout; - fn wrap_line(&self, text: &str, font_size: f32, font_id: FontId) -> Vec; + fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec; } diff --git a/gpui/src/platform/mac/fonts.rs b/gpui/src/platform/mac/fonts.rs index 67f8c7b7a515bca60be1f8a0a87dca4987d179cf..b4ca054470a4ee1ec44d9d2ee3ec057aa67e9ba1 100644 --- a/gpui/src/platform/mac/fonts.rs +++ b/gpui/src/platform/mac/fonts.rs @@ -11,7 +11,8 @@ use crate::{ }; use cocoa::appkit::{CGFloat, CGPoint}; use core_foundation::{ - attributed_string::CFMutableAttributedString, + array::CFIndex, + attributed_string::{CFAttributedStringRef, CFMutableAttributedString}, base::{CFRange, TCFType}, number::CFNumber, string::CFString, @@ -22,7 +23,7 @@ use core_graphics::{ use core_text::{line::CTLine, string_attributes::kCTFontAttributeName}; use font_kit::{canvas::RasterizationOptions, hinting::HintingOptions, source::SystemSource}; use parking_lot::RwLock; -use std::{cell::RefCell, char, convert::TryFrom}; +use std::{cell::RefCell, char, convert::TryFrom, ffi::c_void}; #[allow(non_upper_case_globals)] const kCGImageAlphaOnly: u32 = 7; @@ -86,8 +87,8 @@ impl platform::FontSystem for FontSystem { self.0.read().layout_line(text, font_size, runs) } - fn wrap_line(&self, text: &str, font_size: f32, font_id: FontId) -> Vec { - self.0.read().wrap_line(text, font_size, font_id) + fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec { + self.0.read().wrap_line(text, font_id, font_size, width) } } @@ -194,20 +195,9 @@ impl FontSystemState { ) -> LineLayout { let font_id_attr_name = CFString::from_static_string("zed_font_id"); - let len = text.len(); - let mut utf8_and_utf16_ixs = text.char_indices().chain(Some((len, '\0'))).map({ - let mut utf16_ix = 0; - move |(utf8_ix, c)| { - let result = (utf8_ix, utf16_ix); - utf16_ix += c.len_utf16(); - result - } - }); - // Construct the attributed string, converting UTF8 ranges to UTF16 ranges. let mut string = CFMutableAttributedString::new(); { - let mut utf8_and_utf16_ixs = utf8_and_utf16_ixs.clone(); string.replace_str(&CFString::new(text), CFRange::init(0, 0)); let last_run: RefCell> = Default::default(); @@ -232,19 +222,16 @@ impl FontSystemState { }) .chain(std::iter::from_fn(|| last_run.borrow_mut().take())); - let mut utf8_ix = 0; - let mut utf16_ix = 0; + let mut ix_converter = StringIndexConverter::new(text); for (run_len, font_id) in font_runs { - let utf8_end = utf8_ix + run_len; - let utf16_start = utf16_ix; - while utf8_ix < utf8_end { - let (next_utf8_ix, next_utf16_ix) = utf8_and_utf16_ixs.next().unwrap(); - utf8_ix = next_utf8_ix; - utf16_ix = next_utf16_ix; - } - - let cf_range = - CFRange::init(utf16_start as isize, (utf16_ix - utf16_start) as isize); + let utf8_end = ix_converter.utf8_ix + run_len; + let utf16_start = ix_converter.utf16_ix; + ix_converter.advance_to_utf8_ix(utf8_end); + + let cf_range = CFRange::init( + utf16_start as isize, + (ix_converter.utf16_ix - utf16_start) as isize, + ); let font = &self.fonts[font_id.0]; unsafe { string.set_attribute( @@ -264,8 +251,6 @@ impl FontSystemState { // Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets. let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef()); - let mut utf8_ix = 0; - let mut utf16_ix = 0; let mut runs = Vec::new(); for run in line.glyph_runs().into_iter() { let font_id = FontId( @@ -278,6 +263,7 @@ impl FontSystemState { .unwrap() as usize, ); + let mut ix_converter = StringIndexConverter::new(text); let mut glyphs = Vec::new(); for ((glyph_id, position), glyph_utf16_ix) in run .glyphs() @@ -286,15 +272,11 @@ impl FontSystemState { .zip(run.string_indices().iter()) { let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap(); - while utf16_ix < glyph_utf16_ix { - let (next_utf8_ix, next_utf16_ix) = utf8_and_utf16_ixs.next().unwrap(); - utf8_ix = next_utf8_ix; - utf16_ix = next_utf16_ix; - } + ix_converter.advance_to_utf16_ix(glyph_utf16_ix); glyphs.push(Glyph { id: *glyph_id as GlyphId, position: vec2f(position.x as f32, position.y as f32), - index: utf8_ix, + index: ix_converter.utf8_ix, }); } @@ -308,11 +290,11 @@ impl FontSystemState { descent: typographic_bounds.descent as f32, runs, font_size, - len, + len: text.len(), } } - fn wrap_line(&self, text: &str, font_size: f32, font_id: FontId) -> Vec { + fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec { let mut string = CFMutableAttributedString::new(); string.replace_str(&CFString::new(text), CFRange::init(0, 0)); let cf_range = CFRange::init(0 as isize, text.encode_utf16().count() as isize); @@ -323,12 +305,79 @@ impl FontSystemState { kCTFontAttributeName, &font.native_font().clone_with_font_size(font_size as f64), ); + + let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef()); + let mut ix_converter = StringIndexConverter::new(text); + let mut break_indices = Vec::new(); + while ix_converter.utf8_ix < text.len() { + let utf16_len = CTTypesetterSuggestLineBreak( + typesetter, + ix_converter.utf16_ix as isize, + width as f64, + ) as usize; + ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len); + break_indices.push(ix_converter.utf8_ix as usize); + } + break_indices } + } +} + +#[derive(Clone)] +struct StringIndexConverter<'a> { + text: &'a str, + utf8_ix: usize, + utf16_ix: usize, +} + +impl<'a> StringIndexConverter<'a> { + fn new(text: &'a str) -> Self { + Self { + text, + utf8_ix: 0, + utf16_ix: 0, + } + } + + fn advance_to_utf8_ix(&mut self, utf8_target: usize) { + for (ix, c) in self.text[self.utf8_ix..].char_indices() { + if self.utf8_ix + ix >= utf8_target { + self.utf8_ix += ix; + return; + } + self.utf16_ix += c.len_utf16(); + } + self.utf8_ix = self.text.len(); + } - Vec::new() + fn advance_to_utf16_ix(&mut self, utf16_target: usize) { + for (ix, c) in self.text[self.utf8_ix..].char_indices() { + if self.utf16_ix >= utf16_target { + self.utf8_ix += ix; + return; + } + self.utf16_ix += c.len_utf16(); + } + self.utf8_ix = self.text.len(); } } +#[repr(C)] +pub struct __CFTypesetter(c_void); + +pub type CTTypesetterRef = *const __CFTypesetter; + +#[link(name = "CoreText", kind = "framework")] +extern "C" { + fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef; + + fn CTTypesetterSuggestLineBreak( + typesetter: CTTypesetterRef, + start_index: CFIndex, + width: f64, + ) -> CFIndex; +} + #[cfg(test)] mod tests { use crate::MutableAppContext; @@ -429,4 +478,29 @@ mod tests { writer.write_image_data(&bytes).unwrap(); } } + + #[test] + fn test_layout_line() { + let fonts = FontSystem::new(); + let font_ids = fonts.load_family("Helvetica").unwrap(); + let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); + + let line = "one two three four five"; + let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0); + assert_eq!( + wrap_boundaries, + &["one two ".len(), "one two three ".len(), line.len()] + ); + + let line = "aaa ααα ✋✋✋ 🎉🎉🎉"; + let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0); + assert_eq!( + wrap_boundaries, + &[ + "aaa ααα ".len(), + "aaa ααα ✋✋✋ ".len(), + "aaa ααα ✋✋✋ 🎉🎉🎉".len(), + ] + ); + } }