diff --git a/Cargo.toml b/Cargo.toml index 59f3cd6..e61729a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,10 @@ name = "crossfont" version = "0.8.0" description = "Cross platform native font loading and rasterization" -authors = ["Christian Duerr ", "Joe Wilm "] +authors = [ + "Christian Duerr ", + "Joe Wilm ", +] repository = "https://github.com/alacritty/crossfont.git" documentation = "https://docs.rs/crossfont" license = "Apache-2.0" @@ -13,29 +16,52 @@ edition = "2021" rust-version = "1.77.0" [dependencies] -libc = "0.2" -foreign-types = "0.5" log = "0.4" [target.'cfg(not(any(target_os = "macos", windows)))'.dependencies] yeslogic-fontconfig-sys = "6.0.0" freetype-rs = "0.36.0" +foreign-types = "0.5" [target.'cfg(not(any(target_os = "macos", windows)))'.build-dependencies] pkg-config = "0.3" [target.'cfg(target_os = "macos")'.dependencies] -core-foundation = "0.10.0" -core-text = "21.0.0" -core-graphics = "0.24.0" -core-foundation-sys = "0.8.4" once_cell = "1.12" -objc2 = "0.5.1" -objc2-foundation = { version = "0.2.2", features = [ +objc2 = "0.6.1" +objc2-foundation = { version = "0.3.1", default-features = false, features = [ + "std", "NSString", "NSUserDefaults", "NSValue", ] } +objc2-core-foundation = { version = "0.3.1", default-features = false, features = [ + "std", + "CFArray", + "CFBase", + "CFCGTypes", + "CFDictionary", + "CFNumber", + "CFSet", + "CFString", + "CFURL", +] } +objc2-core-text = { version = "0.3.1", default-features = false, features = [ + "std", + "objc2-core-graphics", + "CTFont", + "CTFontCollection", + "CTFontDescriptor", + "CTFontTraits", +] } +objc2-core-graphics = { version = "0.3.1", default-features = false, features = [ + "std", + "CGBitmapContext", + "CGColorSpace", + "CGContext", + "CGFont", + "CGImage", +] } [target.'cfg(windows)'.dependencies] dwrote = { version = "0.11" } diff --git a/src/darwin/byte_order.rs b/src/darwin/byte_order.rs index 3cd767f..1fc6373 100644 --- a/src/darwin/byte_order.rs +++ b/src/darwin/byte_order.rs @@ -1,15 +1,5 @@ //! Constants for bitmap byte order. -#![allow(non_upper_case_globals)] -pub const kCGBitmapByteOrder32Little: u32 = 2 << 12; -pub const kCGBitmapByteOrder32Big: u32 = 4 << 12; - -#[cfg(target_endian = "little")] -pub const kCGBitmapByteOrder32Host: u32 = kCGBitmapByteOrder32Little; - -#[cfg(target_endian = "big")] -pub const kCGBitmapByteOrder32Host: u32 = kCGBitmapByteOrder32Big; - #[cfg(target_endian = "little")] pub fn extract_rgba(bytes: &[u8]) -> Vec { let pixels = bytes.len() / 4; diff --git a/src/darwin/mod.rs b/src/darwin/mod.rs index 67ccb86..7acb234 100644 --- a/src/darwin/mod.rs +++ b/src/darwin/mod.rs @@ -1,37 +1,32 @@ //! Font rendering based on CoreText. use std::collections::HashMap; -use std::ffi::CStr; +use std::ffi::{c_void, CStr}; use std::iter; use std::path::PathBuf; -use std::ptr; - -use core_foundation::array::{CFArray, CFIndex}; -use core_foundation::base::{CFType, ItemRef, TCFType}; -use core_foundation::number::{CFNumber, CFNumberRef}; -use core_foundation::string::CFString; -use core_graphics::base::kCGImageAlphaPremultipliedFirst; -use core_graphics::color_space::CGColorSpace; -use core_graphics::context::CGContext; -use core_graphics::font::CGGlyph; -use core_graphics::geometry::{CGPoint, CGRect, CGSize}; -use core_text::font::{ - cascade_list_for_languages as ct_cascade_list_for_languages, - new_from_descriptor as ct_new_from_descriptor, new_from_name, CTFont, +use std::ptr::{self, NonNull}; +use std::slice; + +use objc2::rc::autoreleasepool; +use objc2_core_foundation::{ + kCFTypeSetCallBacks, CFArray, CFDictionary, CFIndex, CFNumber, CFRetained, CFSet, CFString, + CGFloat, CGPoint, CGRect, CGSize, Type, CFURL, +}; +use objc2_core_graphics::{ + CGBitmapContextCreate, CGBitmapContextGetBytesPerRow, CGBitmapContextGetData, + CGBitmapContextGetHeight, CGBitmapInfo, CGColorSpace, CGContext, CGGlyph, CGImageAlphaInfo, }; -use core_text::font_collection::create_for_family; -use core_text::font_descriptor::{ - self, kCTFontColorGlyphsTrait, kCTFontDefaultOrientation, kCTFontEnabledAttribute, - CTFontDescriptor, SymbolicTraitAccessors, +use objc2_core_text::{ + kCTFontCollectionRemoveDuplicatesOption, kCTFontEnabledAttribute, kCTFontFamilyNameAttribute, + kCTFontStyleNameAttribute, kCTFontURLAttribute, CTFont, CTFontCollection, CTFontDescriptor, + CTFontOrientation, CTFontSymbolicTraits, }; -use objc2::rc::{autoreleasepool, Retained}; -use objc2_foundation::{ns_string, NSNumber, NSObject, NSObjectProtocol, NSString, NSUserDefaults}; +use objc2_foundation::{ns_string, NSNumber, NSUserDefaults}; use log::{trace, warn}; use once_cell::sync::Lazy; pub mod byte_order; -use byte_order::kCGBitmapByteOrder32Host; use super::{ BitmapBuffer, Error, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, @@ -50,21 +45,27 @@ struct Descriptor { style_name: String, font_path: PathBuf, - ct_descriptor: CTFontDescriptor, + ct_descriptor: CFRetained, } impl Descriptor { - fn new(desc: CTFontDescriptor) -> Descriptor { - Descriptor { - style_name: desc.style_name(), - font_path: desc.font_path().unwrap_or_default(), - ct_descriptor: desc, - } + fn new(desc: &CTFontDescriptor) -> Descriptor { + let style_name = unsafe { desc.attribute(kCTFontStyleNameAttribute) } + .expect("a font must have a non-null style name") + .downcast::() + .unwrap(); + let font_path = unsafe { desc.attribute(kCTFontURLAttribute) } + .and_then(|attr| attr.downcast::().ok()) + .map(|url| url.to_file_path().unwrap()) + .unwrap_or_default(); + Descriptor { style_name: style_name.to_string(), font_path, ct_descriptor: desc.retain() } } /// Create a Font from this descriptor. fn to_font(&self, size: f64, load_fallbacks: bool) -> Font { - let ct_font = ct_new_from_descriptor(&self.ct_descriptor, size); + let ct_font = unsafe { + CTFont::with_font_descriptor(&self.ct_descriptor, size as CGFloat, ptr::null()) + }; let fallbacks = if load_fallbacks { // TODO fixme, hardcoded en for english. @@ -80,9 +81,9 @@ impl Descriptor { // many chars. We add the symbols back in. // Investigate if we can actually use the .-prefixed // fallbacks somehow. - if let Ok(apple_symbols) = new_from_name("Apple Symbols", size) { - fallbacks.push(Font { ct_font: apple_symbols, fallbacks: Vec::new() }) - }; + let name = CFString::from_static_str("Apple Symbols"); + let apple_symbols = unsafe { CTFont::with_name(&name, size, ptr::null_mut()) }; + fallbacks.push(Font { ct_font: apple_symbols, fallbacks: Vec::new() }); fallbacks } else { @@ -210,34 +211,31 @@ impl CoreTextRasterizer { /// Return fallback descriptors for font/language list. fn cascade_list_for_languages(ct_font: &CTFont, languages: &[String]) -> Vec { // Convert language type &Vec -> CFArray. - let langarr: CFArray = { - let tmp: Vec = languages.iter().map(|language| CFString::new(language)).collect(); - CFArray::from_CFTypes(&tmp) + let langarr = { + let tmp: Vec<_> = languages.iter().map(|language| CFString::from_str(language)).collect(); + CFArray::from_retained_objects(&tmp) }; - // CFArray of CTFontDescriptorRef (again). - let list = ct_cascade_list_for_languages(ct_font, &langarr); + let list = + unsafe { ct_font.default_cascade_list_for_languages(Some(langarr.as_opaque())) }.unwrap(); + // CFArray of CTFontDescriptorRef. + let list = unsafe { CFRetained::cast_unchecked::>(list) }; // Convert CFArray to Vec. - list.into_iter().filter(is_enabled).map(|fontdesc| Descriptor::new(fontdesc.clone())).collect() + list.iter().filter(|desc| is_enabled(desc)).map(|desc| Descriptor::new(&desc)).collect() } /// Check if a font is enabled. -fn is_enabled(fontdesc: &ItemRef<'_, CTFontDescriptor>) -> bool { - unsafe { - let descriptor = fontdesc.as_concrete_TypeRef(); - let attr_val = - font_descriptor::CTFontDescriptorCopyAttribute(descriptor, kCTFontEnabledAttribute); - - if attr_val.is_null() { - return false; - } +fn is_enabled(fontdesc: &CTFontDescriptor) -> bool { + let Some(attr_val) = (unsafe { fontdesc.attribute(kCTFontEnabledAttribute) }) else { + return false; + }; - let attr_val = CFType::wrap_under_create_rule(attr_val); - let attr_val = CFNumber::wrap_under_get_rule(attr_val.as_CFTypeRef() as CFNumberRef); + let Ok(attr_val) = attr_val.downcast::() else { + return false; + }; - attr_val.to_i32().unwrap_or(0) != 0 - } + attr_val.as_i32().unwrap_or(0) != 0 } /// Get descriptors for family name. @@ -251,17 +249,39 @@ fn descriptors_for_family(family: &str) -> Vec { create_for_family("Menlo").expect("Menlo exists") }); - // CFArray of CTFontDescriptorRef (i think). - let descriptors = ct_collection.get_descriptors(); + let descriptors = unsafe { ct_collection.matching_font_descriptors() }; if let Some(descriptors) = descriptors { - for descriptor in descriptors.iter() { - out.push(Descriptor::new(descriptor.clone())); + // CFArray of CTFontDescriptorRef (i think). + let descriptors = + unsafe { CFRetained::cast_unchecked::>(descriptors) }; + + for descriptor in descriptors { + out.push(Descriptor::new(&descriptor)); } } out } +pub fn create_for_family(family: &str) -> Option> { + let family_attr = unsafe { kCTFontFamilyNameAttribute }; + let family_name = CFString::from_str(family); + let specified_attrs = CFDictionary::from_slices(&[family_attr], &[&*family_name]); + + let wildcard_desc = unsafe { CTFontDescriptor::with_attributes(specified_attrs.as_opaque()) }; + let ptr = [family_attr].as_ptr().cast_mut().cast::<*const c_void>(); + let mandatory_attrs = unsafe { CFSet::new(None, ptr, 1, &kCFTypeSetCallBacks).unwrap() }; + let matched_descs = unsafe { + CTFontDescriptor::matching_font_descriptors(&wildcard_desc, Some(&mandatory_attrs)) + }?; + let key = unsafe { kCTFontCollectionRemoveDuplicatesOption }; + let value = CFNumber::new_i64(1); + let options = CFDictionary::from_slices(&[key], &[&*value]); + Some(unsafe { + CTFontCollection::with_font_descriptors(Some(&matched_descs), Some(&options.as_opaque())) + }) +} + // The AppleFontSmoothing user default controls font smoothing on macOS, which increases the stroke // width. By default it is unset, and the system behaves as though it is set to 2, which means a // medium level of font smoothing. The valid values are integers from 0 to 3. Any other type, @@ -280,14 +300,7 @@ static FONT_SMOOTHING_ENABLED: Lazy = Lazy::new(|| { None => return true, }; - // SAFETY: The values in `NSUserDefaults` are always subclasses of - // `NSObject`. - let value: Retained = unsafe { Retained::cast(value) }; - - if value.is_kind_of::() { - // SAFETY: Just checked that the value is a NSNumber - let value: Retained = unsafe { Retained::cast(value) }; - + if let Some(value) = value.downcast_ref::() { // NSNumber's objCType method returns one of these strings depending on the size: // q = quad (long long), l = long, i = int, s = short. // This is done to reject booleans, which are NSNumbers with an objCType of "c", but @@ -302,10 +315,8 @@ static FONT_SMOOTHING_ENABLED: Lazy = Lazy::new(|| { let smoothing = value.integerValue(); smoothing != 0 - } else if value.is_kind_of::() { - // SAFETY: Just checked that the value is a NSString - let value: Retained = unsafe { Retained::cast(value) }; - let smoothing = unsafe { value.integerValue() }; + } else if let Ok(value) = value.downcast::() { + let smoothing = value.integerValue(); smoothing != 0 } else { true @@ -316,7 +327,7 @@ static FONT_SMOOTHING_ENABLED: Lazy = Lazy::new(|| { /// A font. #[derive(Clone)] struct Font { - ct_font: CTFont, + ct_font: CFRetained, fallbacks: Vec, } @@ -326,15 +337,15 @@ impl Font { fn metrics(&self) -> Metrics { let average_advance = self.glyph_advance('0'); - let ascent = self.ct_font.ascent().round(); - let descent = self.ct_font.descent().round(); - let leading = self.ct_font.leading().round(); + let ascent = unsafe { self.ct_font.ascent() }.round(); + let descent = unsafe { self.ct_font.descent() }.round(); + let leading = unsafe { self.ct_font.leading() }.round(); let line_height = ascent + descent + leading; // Strikeout and underline metrics. // CoreText doesn't provide strikeout so we provide our own. - let underline_position = self.ct_font.underline_position() as f32; - let underline_thickness = self.ct_font.underline_thickness() as f32; + let underline_position = unsafe { self.ct_font.underline_position() } as f32; + let underline_thickness = unsafe { self.ct_font.underline_thickness() } as f32; let strikeout_position = (line_height / 2. - descent) as f32; let strikeout_thickness = underline_thickness; @@ -350,15 +361,15 @@ impl Font { } fn is_bold(&self) -> bool { - self.ct_font.symbolic_traits().is_bold() + unsafe { self.ct_font.symbolic_traits() }.contains(CTFontSymbolicTraits::BoldTrait) } fn is_italic(&self) -> bool { - self.ct_font.symbolic_traits().is_italic() + unsafe { self.ct_font.symbolic_traits() }.contains(CTFontSymbolicTraits::ItalicTrait) } fn is_colored(&self) -> bool { - (self.ct_font.symbolic_traits() & kCTFontColorGlyphsTrait) != 0 + unsafe { self.ct_font.symbolic_traits() }.contains(CTFontSymbolicTraits::ColorGlyphsTrait) } fn glyph_advance(&self, character: char) -> f64 { @@ -367,9 +378,9 @@ impl Font { let indices = [index as CGGlyph]; unsafe { - self.ct_font.get_advances_for_glyphs( - kCTFontDefaultOrientation, - &indices[0], + self.ct_font.advances_for_glyphs( + CTFontOrientation::Default, + NonNull::from(&indices[0]), ptr::null_mut(), 1, ) @@ -377,9 +388,15 @@ impl Font { } fn get_glyph(&self, character: char, glyph_index: u32) -> RasterizedGlyph { - let bounds = self - .ct_font - .get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph_index as CGGlyph]); + let glyphs = [glyph_index as CGGlyph]; + let bounds = unsafe { + self.ct_font.bounding_rects_for_glyphs( + CTFontOrientation::Default, + NonNull::from(&glyphs[0]), + ptr::null_mut(), + 1, + ) + }; let rasterized_left = bounds.origin.x.floor() as i32; let rasterized_width = @@ -400,50 +417,65 @@ impl Font { }; } - let mut cg_context = CGContext::create_bitmap_context( - None, - rasterized_width as usize, - rasterized_height as usize, - 8, // bits per component - rasterized_width as usize * 4, - &CGColorSpace::create_device_rgb(), - kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, - ); + let cg_context = unsafe { + CGBitmapContextCreate( + ptr::null_mut(), + rasterized_width as usize, + rasterized_height as usize, + 8, // bits per component + rasterized_width as usize * 4, + CGColorSpace::new_device_rgb().as_deref(), + CGImageAlphaInfo::PremultipliedFirst.0 | CGBitmapInfo::ByteOrder32Host.0, + ) + } + .unwrap(); let is_colored = self.is_colored(); // Set background color for graphics context. let bg_a = if is_colored { 0.0 } else { 1.0 }; - cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, bg_a); + unsafe { CGContext::set_rgb_fill_color(Some(&cg_context), 0.0, 0.0, 0.0, bg_a) }; let context_rect = CGRect::new( - &CGPoint::new(0.0, 0.0), - &CGSize::new(f64::from(rasterized_width), f64::from(rasterized_height)), + CGPoint::new(0.0, 0.0), + CGSize::new(f64::from(rasterized_width), f64::from(rasterized_height)), ); - cg_context.fill_rect(context_rect); + unsafe { CGContext::fill_rect(Some(&cg_context), context_rect) }; - cg_context.set_allows_font_smoothing(true); - cg_context.set_should_smooth_fonts(*FONT_SMOOTHING_ENABLED); - cg_context.set_allows_font_subpixel_quantization(true); - cg_context.set_should_subpixel_quantize_fonts(true); - cg_context.set_allows_font_subpixel_positioning(true); - cg_context.set_should_subpixel_position_fonts(true); - cg_context.set_allows_antialiasing(true); - cg_context.set_should_antialias(true); + unsafe { + CGContext::set_allows_font_smoothing(Some(&cg_context), true); + CGContext::set_should_smooth_fonts(Some(&cg_context), *FONT_SMOOTHING_ENABLED); + CGContext::set_allows_font_subpixel_quantization(Some(&cg_context), true); + CGContext::set_should_subpixel_quantize_fonts(Some(&cg_context), true); + CGContext::set_allows_font_subpixel_positioning(Some(&cg_context), true); + CGContext::set_should_subpixel_position_fonts(Some(&cg_context), true); + CGContext::set_allows_antialiasing(Some(&cg_context), true); + CGContext::set_should_antialias(Some(&cg_context), true); + } // Set fill color to white for drawing the glyph. - cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0); + unsafe { CGContext::set_rgb_fill_color(Some(&cg_context), 1.0, 1.0, 1.0, 1.0) }; let rasterization_origin = CGPoint { x: f64::from(-rasterized_left), y: f64::from(rasterized_descent) }; - self.ct_font.draw_glyphs( - &[glyph_index as CGGlyph], - &[rasterization_origin], - cg_context.clone(), - ); + unsafe { + self.ct_font.draw_glyphs( + NonNull::from(&[glyph_index as CGGlyph][0]), + NonNull::from(&[rasterization_origin][0]), + 1, + &cg_context, + ) + }; - let rasterized_pixels = cg_context.data().to_vec(); + let rasterized_pixels = unsafe { + slice::from_raw_parts_mut( + CGBitmapContextGetData(Some(&cg_context)) as *mut u8, + CGBitmapContextGetHeight(Some(&cg_context)) + * CGBitmapContextGetBytesPerRow(Some(&cg_context)), + ) + }; + let rasterized_pixels = rasterized_pixels.to_vec(); let buffer = if is_colored { BitmapBuffer::Rgba(byte_order::extract_rgba(&rasterized_pixels)) @@ -477,9 +509,9 @@ impl Font { let mut glyphs: [CGGlyph; 2] = [0; 2]; let res = unsafe { - self.ct_font.get_glyphs_for_characters( - encoded.as_ptr(), - glyphs.as_mut_ptr(), + self.ct_font.glyphs_for_characters( + NonNull::new(encoded.as_ptr().cast_mut()).unwrap(), + NonNull::new(glyphs.as_mut_ptr()).unwrap(), encoded.len() as CFIndex, ) }; diff --git a/src/ft/fc/object_set.rs b/src/ft/fc/object_set.rs index 74faabb..85f5875 100644 --- a/src/ft/fc/object_set.rs +++ b/src/ft/fc/object_set.rs @@ -1,7 +1,6 @@ +use std::ffi::c_char; use std::ptr::NonNull; -use libc::c_char; - use super::ffi::{FcObjectSet, FcObjectSetAdd, FcObjectSetCreate, FcObjectSetDestroy}; use foreign_types::{foreign_type, ForeignTypeRef}; diff --git a/src/ft/fc/pattern.rs b/src/ft/fc/pattern.rs index 74ec178..0dc2b43 100644 --- a/src/ft/fc/pattern.rs +++ b/src/ft/fc/pattern.rs @@ -1,11 +1,10 @@ -use std::ffi::{CStr, CString}; +use std::ffi::{c_char, c_double, c_int, CStr, CString}; use std::fmt; use std::path::PathBuf; use std::ptr::{self, NonNull}; use std::str; use foreign_types::{foreign_type, ForeignType, ForeignTypeRef}; -use libc::{c_char, c_double, c_int}; use super::ffi::FcMatrix; use super::ffi::FcResultMatch; diff --git a/src/ft/mod.rs b/src/ft/mod.rs index 3710d27..695265b 100644 --- a/src/ft/mod.rs +++ b/src/ft/mod.rs @@ -2,6 +2,7 @@ use std::cmp::{min, Ordering}; use std::collections::HashMap; +use std::ffi::{c_long, c_uint}; use std::fmt::{self, Formatter}; use std::rc::Rc; use std::time::{Duration, Instant}; @@ -10,7 +11,6 @@ use freetype::face::LoadFlag; use freetype::tt_os2::TrueTypeOS2Table; use freetype::{self, Library, Matrix}; use freetype::{freetype_sys, Face as FtFace}; -use libc::{c_long, c_uint}; use log::{debug, trace}; pub mod fc; @@ -71,15 +71,18 @@ impl fmt::Debug for FaceLoadingProperties { f.debug_struct("Face") .field("ft_face", &self.ft_face) .field("load_flags", &self.load_flags) - .field("render_mode", &match self.render_mode { - freetype::RenderMode::Normal => "Normal", - freetype::RenderMode::Light => "Light", - freetype::RenderMode::Mono => "Mono", - freetype::RenderMode::Lcd => "Lcd", - freetype::RenderMode::LcdV => "LcdV", - freetype::RenderMode::Max => "Max", - freetype::RenderMode::Sdf => "Sdf", - }) + .field( + "render_mode", + &match self.render_mode { + freetype::RenderMode::Normal => "Normal", + freetype::RenderMode::Light => "Light", + freetype::RenderMode::Mono => "Mono", + freetype::RenderMode::Lcd => "Lcd", + freetype::RenderMode::LcdV => "LcdV", + freetype::RenderMode::Max => "Max", + freetype::RenderMode::Sdf => "Sdf", + }, + ) .field("lcd_filter", &self.lcd_filter) .finish() }