mod font_id;
mod font_load;
mod rasterize;
mod shaping;
mod system_fonts;
use crate::{
FontHandle, FontMetrics, GlyphBitmap, GlyphBounds, ShapedLine, TextBackend, TextError,
backend::Font, types::FontDescriptor,
};
use objc2_core_foundation::{CFData, CFRetained};
use objc2_core_text::CTFont;
use std::cell::RefCell;
use std::collections::HashMap;
use std::marker::PhantomData;
pub struct CoreTextBackend {
font_registry: RefCell<HashMap<FontHandle, Box<CoreTextFont>>>,
_not_send: PhantomData<*const ()>,
}
impl CoreTextBackend {
pub fn new() -> Result<Self, TextError> {
Ok(Self {
font_registry: RefCell::new(HashMap::new()),
_not_send: PhantomData,
})
}
fn build_substitute_font(
ct_font: CFRetained<CTFont>,
size_lpx: f32,
scale: f32,
handle: FontHandle,
) -> CoreTextFont {
debug_assert!(
{
let actual_pt = unsafe { ct_font.size() } as f32;
(actual_pt - size_lpx).abs() < 0.5
},
"substitute CTFont size disagrees with parent line size_lpx={size_lpx}",
);
let metrics = font_load::extract_metrics(&ct_font);
CoreTextFont {
ct_font,
_data_retain: None,
size_lpx,
scale,
metrics,
handle,
}
}
fn shape_with_direction(
&self,
font: &CoreTextFont,
text: &str,
forced_direction: Option<crate::types::Direction>,
) -> Result<ShapedLine, TextError> {
let result = shaping::shape_line(
&font.ct_font,
text,
&font.metrics,
font.size_lpx,
font.scale,
forced_direction,
)?;
if !result.captured_fonts.is_empty() {
let mut reg = self.font_registry.borrow_mut();
for cf in result.captured_fonts {
reg.entry(cf.handle).or_insert_with(|| {
Box::new(Self::build_substitute_font(
cf.font,
font.size_lpx,
font.scale,
cf.handle,
))
});
}
}
Ok(result.line)
}
}
impl Default for CoreTextBackend {
fn default() -> Self {
Self::new().expect("CoreText backend initialization should never fail")
}
}
pub struct CoreTextFont {
ct_font: CFRetained<CTFont>,
_data_retain: Option<CFRetained<CFData>>,
size_lpx: f32,
scale: f32,
metrics: FontMetrics,
handle: FontHandle,
}
impl Font for CoreTextFont {
fn handle(&self) -> FontHandle {
self.handle
}
fn metrics(&self) -> FontMetrics {
self.metrics
}
fn size_lpx(&self) -> f32 {
self.size_lpx
}
fn scale(&self) -> f32 {
self.scale
}
}
impl TextBackend for CoreTextBackend {
type Font = CoreTextFont;
fn load_font(
&mut self,
family: &str,
_size_lpx: f32,
_scale: f32,
) -> Result<Self::Font, TextError> {
Err(TextError::FontNotFound {
family: family.to_string(),
})
}
fn load_font_from_bytes(
&mut self,
bytes: &'static [u8],
size_lpx: f32,
scale: f32,
) -> Result<Self::Font, TextError> {
let (ct_font, data) = font_load::create_font_from_bytes(bytes, size_lpx)?;
let metrics = font_load::extract_metrics(&ct_font);
let handle = font_id::font_handle_from_ct_font(&ct_font, size_lpx, scale);
Ok(CoreTextFont {
ct_font,
_data_retain: Some(data),
size_lpx,
scale,
metrics,
handle,
})
}
fn shape_line(&self, font: &Self::Font, text: &str) -> Result<ShapedLine, TextError> {
self.shape_with_direction(font, text, None)
}
fn shape_segment(
&self,
font: &Self::Font,
text: &str,
direction: crate::types::Direction,
) -> Result<ShapedLine, TextError> {
self.shape_with_direction(font, text, Some(direction))
}
fn font_for(&self, handle: FontHandle) -> Option<&Self::Font> {
let map = self.font_registry.borrow();
let ptr: *const CoreTextFont = map.get(&handle).map(|b| &**b as *const _)?;
drop(map);
Some(unsafe { &*ptr })
}
fn rasterize_glyph(
&self,
font: &Self::Font,
glyph_id: u32,
variant: u8,
) -> Result<GlyphBitmap, TextError> {
let glyph_id_u16 =
u16::try_from(glyph_id).map_err(|_| TextError::GlyphNotFound { glyph_id })?;
rasterize::rasterize(
&font.ct_font,
glyph_id_u16,
font.size_lpx,
font.scale,
variant,
)
}
fn glyph_raster_bounds(
&self,
font: &Self::Font,
glyph_id: u32,
) -> Result<GlyphBounds, TextError> {
let glyph_id_u16 =
u16::try_from(glyph_id).map_err(|_| TextError::GlyphNotFound { glyph_id })?;
rasterize::get_glyph_bounds(&font.ct_font, glyph_id_u16, font.scale)
}
fn enumerate_system_fonts(&self) -> Result<Vec<FontDescriptor>, TextError> {
system_fonts::enumerate_system_fonts()
}
}