703 lines · 24.6 KB · Rust
RawDownload
use crate::{    Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, FontRun,    FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,    RenderGlyphParams, Result, SUBPIXEL_VARIANTS, ShapedGlyph, ShapedRun, SharedString, Size,    point, px, size, swap_rgba_pa_to_bgra,};use anyhow::anyhow;use cocoa::appkit::CGFloat;use collections::HashMap;use core_foundation::{    attributed_string::CFMutableAttributedString,    base::{CFRange, TCFType},    number::CFNumber,    string::CFString,};use core_graphics::{    base::{CGGlyph, kCGImageAlphaPremultipliedLast},    color_space::CGColorSpace,    context::CGContext,    display::CGPoint,};use core_text::{    font::CTFont,    font_descriptor::{        kCTFontSlantTrait, kCTFontSymbolicTrait, kCTFontWeightTrait, kCTFontWidthTrait,    },    line::CTLine,    string_attributes::kCTFontAttributeName,};use font_kit::{    font::Font as FontKitFont,    handle::Handle,    hinting::HintingOptions,    metrics::Metrics,    properties::{Style as FontkitStyle, Weight as FontkitWeight},    source::SystemSource,    sources::mem::MemSource,};use parking_lot::{RwLock, RwLockUpgradableReadGuard};use pathfinder_geometry::{    rect::{RectF, RectI},    transform2d::Transform2F,    vector::{Vector2F, Vector2I},};use smallvec::SmallVec;use std::{borrow::Cow, char, cmp, convert::TryFrom, sync::Arc}; use super::open_type::apply_features_and_fallbacks; #[allow(non_upper_case_globals)]const kCGImageAlphaOnly: u32 = 7; pub(crate) struct MacTextSystem(RwLock<MacTextSystemState>); #[derive(Clone, PartialEq, Eq, Hash)]struct FontKey {    font_family: SharedString,    font_features: FontFeatures,    font_fallbacks: Option<FontFallbacks>,} struct MacTextSystemState {    memory_source: MemSource,    system_source: SystemSource,    fonts: Vec<FontKitFont>,    font_selections: HashMap<Font, FontId>,    font_ids_by_postscript_name: HashMap<String, FontId>,    font_ids_by_font_key: HashMap<FontKey, SmallVec<[FontId; 4]>>,    postscript_names_by_font_id: HashMap<FontId, String>,} impl MacTextSystem {    pub(crate) fn new() -> Self {        Self(RwLock::new(MacTextSystemState {            memory_source: MemSource::empty(),            system_source: SystemSource::new(),            fonts: Vec::new(),            font_selections: HashMap::default(),            font_ids_by_postscript_name: HashMap::default(),            font_ids_by_font_key: HashMap::default(),            postscript_names_by_font_id: HashMap::default(),        }))    }} impl Default for MacTextSystem {    fn default() -> Self {        Self::new()    }} impl PlatformTextSystem for MacTextSystem {    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {        self.0.write().add_fonts(fonts)    }     fn all_font_names(&self) -> Vec<String> {        let mut names = Vec::new();        let collection = core_text::font_collection::create_for_all_families();        let Some(descriptors) = collection.get_descriptors() else {            return names;        };        for descriptor in descriptors.into_iter() {            names.extend(lenient_font_attributes::family_name(&descriptor));        }        if let Ok(fonts_in_memory) = self.0.read().memory_source.all_families() {            names.extend(fonts_in_memory);        }        names    }     fn font_id(&self, font: &Font) -> Result<FontId> {        let lock = self.0.upgradable_read();        if let Some(font_id) = lock.font_selections.get(font) {            Ok(*font_id)        } else {            let mut lock = RwLockUpgradableReadGuard::upgrade(lock);            let font_key = FontKey {                font_family: font.family.clone(),                font_features: font.features.clone(),                font_fallbacks: font.fallbacks.clone(),            };            let candidates = if let Some(font_ids) = lock.font_ids_by_font_key.get(&font_key) {                font_ids.as_slice()            } else {                let font_ids =                    lock.load_family(&font.family, &font.features, font.fallbacks.as_ref())?;                lock.font_ids_by_font_key.insert(font_key.clone(), font_ids);                lock.font_ids_by_font_key[&font_key].as_ref()            };             let candidate_properties = candidates                .iter()                .map(|font_id| lock.fonts[font_id.0].properties())                .collect::<SmallVec<[_; 4]>>();             let ix = font_kit::matching::find_best_match(                &candidate_properties,                &font_kit::properties::Properties {                    style: font.style.into(),                    weight: font.weight.into(),                    stretch: Default::default(),                },            )?;             let font_id = candidates[ix];            lock.font_selections.insert(font.clone(), font_id);            Ok(font_id)        }    }     fn font_metrics(&self, font_id: FontId) -> FontMetrics {        self.0.read().fonts[font_id.0].metrics().into()    }     fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {        Ok(self.0.read().fonts[font_id.0]            .typographic_bounds(glyph_id.0)?            .into())    }     fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {        self.0.read().advance(font_id, glyph_id)    }     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {        self.0.read().glyph_for_char(font_id, ch)    }     fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {        self.0.read().raster_bounds(params)    }     fn rasterize_glyph(        &self,        glyph_id: &RenderGlyphParams,        raster_bounds: Bounds<DevicePixels>,    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {        self.0.read().rasterize_glyph(glyph_id, raster_bounds)    }     fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {        self.0.write().layout_line(text, font_size, font_runs)    }} impl MacTextSystemState {    fn add_fonts(&mut self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {        let fonts = fonts            .into_iter()            .map(|bytes| match bytes {                Cow::Borrowed(embedded_font) => {                    let data_provider = unsafe {                        core_graphics::data_provider::CGDataProvider::from_slice(embedded_font)                    };                    let font = core_graphics::font::CGFont::from_data_provider(data_provider)                        .map_err(|_| anyhow!("Could not load an embedded font."))?;                    let font = font_kit::loaders::core_text::Font::from_core_graphics_font(font);                    Ok(Handle::from_native(&font))                }                Cow::Owned(bytes) => Ok(Handle::from_memory(Arc::new(bytes), 0)),            })            .collect::<Result<Vec<_>>>()?;        self.memory_source.add_fonts(fonts.into_iter())?;        Ok(())    }     fn load_family(        &mut self,        name: &str,        features: &FontFeatures,        fallbacks: Option<&FontFallbacks>,    ) -> Result<SmallVec<[FontId; 4]>> {        let name = if name == ".SystemUIFont" {            ".AppleSystemUIFont"        } else {            name        };         let mut font_ids = SmallVec::new();        let family = self            .memory_source            .select_family_by_name(name)            .or_else(|_| self.system_source.select_family_by_name(name))?;        for font in family.fonts() {            let mut font = font.load()?;             apply_features_and_fallbacks(&mut font, features, fallbacks)?;            // This block contains a precautionary fix to guard against loading fonts            // that might cause panics due to `.unwrap()`s up the chain.            {                // We use the 'm' character for text measurements in various spots                // (e.g., the editor). However, at time of writing some of those usages                // will panic if the font has no 'm' glyph.                //                // Therefore, we check up front that the font has the necessary glyph.                let has_m_glyph = font.glyph_for_char('m').is_some();                 // HACK: The 'Segoe Fluent Icons' font does not have an 'm' glyph,                // but we need to be able to load it for rendering Windows icons in                // the Storybook (on macOS).                let is_segoe_fluent_icons = font.full_name() == "Segoe Fluent Icons";                 if !has_m_glyph && !is_segoe_fluent_icons {                    // I spent far too long trying to track down why a font missing the 'm'                    // character wasn't loading. This log statement will hopefully save                    // someone else from suffering the same fate.                    log::warn!(                        "font '{}' has no 'm' character and was not loaded",                        font.full_name()                    );                    continue;                }            }             // We've seen a number of panics in production caused by calling font.properties()            // which unwraps a downcast to CFNumber. This is an attempt to avoid the panic,            // and to try and identify the incalcitrant font.            let traits = font.native_font().all_traits();            if unsafe {                !(traits                    .get(kCTFontSymbolicTrait)                    .downcast::<CFNumber>()                    .is_some()                    && traits                        .get(kCTFontWidthTrait)                        .downcast::<CFNumber>()                        .is_some()                    && traits                        .get(kCTFontWeightTrait)                        .downcast::<CFNumber>()                        .is_some()                    && traits                        .get(kCTFontSlantTrait)                        .downcast::<CFNumber>()                        .is_some())            } {                log::error!(                    "Failed to read traits for font {:?}",                    font.postscript_name().unwrap()                );                continue;            }             let font_id = FontId(self.fonts.len());            font_ids.push(font_id);            let postscript_name = font.postscript_name().unwrap();            self.font_ids_by_postscript_name                .insert(postscript_name.clone(), font_id);            self.postscript_names_by_font_id                .insert(font_id, postscript_name);            self.fonts.push(font);        }        Ok(font_ids)    }     fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {        Ok(self.fonts[font_id.0].advance(glyph_id.0)?.into())    }     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {        self.fonts[font_id.0].glyph_for_char(ch).map(GlyphId)    }     fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {        let postscript_name = requested_font.postscript_name();        if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {            *font_id        } else {            let font_id = FontId(self.fonts.len());            self.font_ids_by_postscript_name                .insert(postscript_name.clone(), font_id);            self.postscript_names_by_font_id                .insert(font_id, postscript_name);            self.fonts                .push(font_kit::font::Font::from_core_graphics_font(                    requested_font.copy_to_CGFont(),                ));            font_id        }    }     fn is_emoji(&self, font_id: FontId) -> bool {        self.postscript_names_by_font_id            .get(&font_id)            .map_or(false, |postscript_name| {                postscript_name == "AppleColorEmoji" || postscript_name == ".AppleColorEmojiUI"            })    }     fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {        let font = &self.fonts[params.font_id.0];        let scale = Transform2F::from_scale(params.scale_factor);        Ok(font            .raster_bounds(                params.glyph_id.0,                params.font_size.into(),                scale,                HintingOptions::None,                font_kit::canvas::RasterizationOptions::GrayscaleAa,            )?            .into())    }     fn rasterize_glyph(        &self,        params: &RenderGlyphParams,        glyph_bounds: Bounds<DevicePixels>,    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {        if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {            Err(anyhow!("glyph bounds are empty"))        } else {            // Add an extra pixel when the subpixel variant isn't zero to make room for anti-aliasing.            let mut bitmap_size = glyph_bounds.size;            if params.subpixel_variant.x > 0 {                bitmap_size.width += DevicePixels(1);            }            if params.subpixel_variant.y > 0 {                bitmap_size.height += DevicePixels(1);            }            let bitmap_size = bitmap_size;             let mut bytes;            let cx;            if params.is_emoji {                bytes = vec![0; bitmap_size.width.0 as usize * 4 * bitmap_size.height.0 as usize];                cx = CGContext::create_bitmap_context(                    Some(bytes.as_mut_ptr() as *mut _),                    bitmap_size.width.0 as usize,                    bitmap_size.height.0 as usize,                    8,                    bitmap_size.width.0 as usize * 4,                    &CGColorSpace::create_device_rgb(),                    kCGImageAlphaPremultipliedLast,                );            } else {                bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];                cx = CGContext::create_bitmap_context(                    Some(bytes.as_mut_ptr() as *mut _),                    bitmap_size.width.0 as usize,                    bitmap_size.height.0 as usize,                    8,                    bitmap_size.width.0 as usize,                    &CGColorSpace::create_device_gray(),                    kCGImageAlphaOnly,                );            }             // Move the origin to bottom left and account for scaling, this            // makes drawing text consistent with the font-kit's raster_bounds.            cx.translate(                -glyph_bounds.origin.x.0 as CGFloat,                (glyph_bounds.origin.y.0 + glyph_bounds.size.height.0) as CGFloat,            );            cx.scale(                params.scale_factor as CGFloat,                params.scale_factor as CGFloat,            );             let subpixel_shift = params                .subpixel_variant                .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);            cx.set_allows_font_subpixel_positioning(true);            cx.set_should_subpixel_position_fonts(true);            cx.set_allows_font_subpixel_quantization(false);            cx.set_should_subpixel_quantize_fonts(false);            self.fonts[params.font_id.0]                .native_font()                .clone_with_font_size(f32::from(params.font_size) as CGFloat)                .draw_glyphs(                    &[params.glyph_id.0 as CGGlyph],                    &[CGPoint::new(                        (subpixel_shift.x / params.scale_factor) as CGFloat,                        (subpixel_shift.y / params.scale_factor) as CGFloat,                    )],                    cx,                );             if params.is_emoji {                // Convert from RGBA with premultiplied alpha to BGRA with straight alpha.                for pixel in bytes.chunks_exact_mut(4) {                    swap_rgba_pa_to_bgra(pixel);                }            }             Ok((bitmap_size, bytes))        }    }     fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {        // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.        let mut string = CFMutableAttributedString::new();        {            string.replace_str(&CFString::new(text), CFRange::init(0, 0));            let utf16_line_len = string.char_len() as usize;             let mut ix_converter = StringIndexConverter::new(text);            for run in font_runs {                let utf8_end = ix_converter.utf8_ix + run.len;                let utf16_start = ix_converter.utf16_ix;                 if utf16_start >= utf16_line_len {                    break;                }                 ix_converter.advance_to_utf8_ix(utf8_end);                let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);                 let cf_range =                    CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);                 let font: &FontKitFont = &self.fonts[run.font_id.0];                 unsafe {                    string.set_attribute(                        cf_range,                        kCTFontAttributeName,                        &font.native_font().clone_with_font_size(font_size.into()),                    );                }                 if utf16_end == utf16_line_len {                    break;                }            }        }         // 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 glyph_runs = line.glyph_runs();        let mut runs = Vec::with_capacity(glyph_runs.len() as usize);        let mut ix_converter = StringIndexConverter::new(text);        for run in glyph_runs.into_iter() {            let attributes = run.attributes().unwrap();            let font = unsafe {                attributes                    .get(kCTFontAttributeName)                    .downcast::<CTFont>()                    .unwrap()            };            let font_id = self.id_for_native_font(font);             let mut glyphs = SmallVec::new();            for ((glyph_id, position), glyph_utf16_ix) in run                .glyphs()                .iter()                .zip(run.positions().iter())                .zip(run.string_indices().iter())            {                let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();                if ix_converter.utf16_ix > glyph_utf16_ix {                    // We cannot reuse current index converter, as it can only seek forward. Restart the search.                    ix_converter = StringIndexConverter::new(text);                }                ix_converter.advance_to_utf16_ix(glyph_utf16_ix);                glyphs.push(ShapedGlyph {                    id: GlyphId(*glyph_id as u32),                    position: point(position.x as f32, position.y as f32).map(px),                    index: ix_converter.utf8_ix,                    is_emoji: self.is_emoji(font_id),

Lines 1–500 of 703.