fonts.rs

  1use crate::{
  2    fonts::{FontId, GlyphId, Metrics, Properties},
  3    geometry::{
  4        rect::{RectF, RectI},
  5        transform2d::Transform2F,
  6        vector::{vec2f, vec2i, Vector2F},
  7    },
  8    platform,
  9    text_layout::{Glyph, Line, Run},
 10};
 11use cocoa::appkit::{CGFloat, CGPoint};
 12use core_foundation::{
 13    attributed_string::CFMutableAttributedString,
 14    base::{CFRange, TCFType},
 15    number::CFNumber,
 16    string::CFString,
 17};
 18use core_graphics::{
 19    base::CGGlyph, color_space::CGColorSpace, context::CGContext, geometry::CGAffineTransform,
 20};
 21use core_text::{line::CTLine, string_attributes::kCTFontAttributeName};
 22use font_kit::{canvas::RasterizationOptions, hinting::HintingOptions, source::SystemSource};
 23use parking_lot::RwLock;
 24use std::{char, convert::TryFrom};
 25
 26#[allow(non_upper_case_globals)]
 27const kCGImageAlphaOnly: u32 = 7;
 28
 29pub struct FontSystem(RwLock<FontSystemState>);
 30
 31struct FontSystemState {
 32    source: SystemSource,
 33    fonts: Vec<font_kit::font::Font>,
 34}
 35
 36impl FontSystem {
 37    pub fn new() -> Self {
 38        Self(RwLock::new(FontSystemState {
 39            source: SystemSource::new(),
 40            fonts: Vec::new(),
 41        }))
 42    }
 43}
 44
 45impl platform::FontSystem for FontSystem {
 46    fn load_family(&self, name: &str) -> anyhow::Result<Vec<FontId>> {
 47        self.0.write().load_family(name)
 48    }
 49
 50    fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
 51        self.0.read().select_font(font_ids, properties)
 52    }
 53
 54    fn font_metrics(&self, font_id: FontId) -> Metrics {
 55        self.0.read().font_metrics(font_id)
 56    }
 57
 58    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
 59        self.0.read().typographic_bounds(font_id, glyph_id)
 60    }
 61
 62    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
 63        self.0.read().glyph_for_char(font_id, ch)
 64    }
 65
 66    fn rasterize_glyph(
 67        &self,
 68        font_id: FontId,
 69        font_size: f32,
 70        glyph_id: GlyphId,
 71        subpixel_shift: Vector2F,
 72        scale_factor: f32,
 73    ) -> Option<(RectI, Vec<u8>)> {
 74        self.0
 75            .read()
 76            .rasterize_glyph(font_id, font_size, glyph_id, subpixel_shift, scale_factor)
 77    }
 78
 79    fn layout_str(
 80        &self,
 81        text: &str,
 82        font_size: f32,
 83        runs: &[(std::ops::Range<usize>, FontId)],
 84    ) -> Line {
 85        self.0.read().layout_str(text, font_size, runs)
 86    }
 87}
 88
 89impl FontSystemState {
 90    fn load_family(&mut self, name: &str) -> anyhow::Result<Vec<FontId>> {
 91        let mut font_ids = Vec::new();
 92        for font in self.source.select_family_by_name(name)?.fonts() {
 93            let font = font.load()?;
 94            font_ids.push(FontId(self.fonts.len()));
 95            self.fonts.push(font);
 96        }
 97        Ok(font_ids)
 98    }
 99
100    fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
101        let candidates = font_ids
102            .iter()
103            .map(|font_id| self.fonts[font_id.0].properties())
104            .collect::<Vec<_>>();
105        let idx = font_kit::matching::find_best_match(&candidates, properties)?;
106        Ok(font_ids[idx])
107    }
108
109    fn font_metrics(&self, font_id: FontId) -> Metrics {
110        self.fonts[font_id.0].metrics()
111    }
112
113    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
114        Ok(self.fonts[font_id.0].typographic_bounds(glyph_id)?)
115    }
116
117    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
118        self.fonts[font_id.0].glyph_for_char(ch)
119    }
120
121    fn rasterize_glyph(
122        &self,
123        font_id: FontId,
124        font_size: f32,
125        glyph_id: GlyphId,
126        subpixel_shift: Vector2F,
127        scale_factor: f32,
128    ) -> Option<(RectI, Vec<u8>)> {
129        let font = &self.fonts[font_id.0];
130        let scale = Transform2F::from_scale(scale_factor);
131        let bounds = font
132            .raster_bounds(
133                glyph_id,
134                font_size,
135                scale,
136                HintingOptions::None,
137                RasterizationOptions::GrayscaleAa,
138            )
139            .ok()?;
140
141        if bounds.width() == 0 || bounds.height() == 0 {
142            None
143        } else {
144            // Make room for subpixel variants.
145            let bounds = RectI::new(bounds.origin(), bounds.size() + vec2i(1, 1));
146            let mut pixels = vec![0; bounds.width() as usize * bounds.height() as usize];
147            let ctx = CGContext::create_bitmap_context(
148                Some(pixels.as_mut_ptr() as *mut _),
149                bounds.width() as usize,
150                bounds.height() as usize,
151                8,
152                bounds.width() as usize,
153                &CGColorSpace::create_device_gray(),
154                kCGImageAlphaOnly,
155            );
156
157            // Move the origin to bottom left and account for scaling, this
158            // makes drawing text consistent with the font-kit's raster_bounds.
159            ctx.translate(0.0, bounds.height() as CGFloat);
160            let transform = scale.translate(-bounds.origin().to_f32());
161            ctx.set_text_matrix(&CGAffineTransform {
162                a: transform.matrix.m11() as CGFloat,
163                b: -transform.matrix.m21() as CGFloat,
164                c: -transform.matrix.m12() as CGFloat,
165                d: transform.matrix.m22() as CGFloat,
166                tx: transform.vector.x() as CGFloat,
167                ty: -transform.vector.y() as CGFloat,
168            });
169
170            ctx.set_font(&font.native_font().copy_to_CGFont());
171            ctx.set_font_size(font_size as CGFloat);
172            ctx.show_glyphs_at_positions(
173                &[glyph_id as CGGlyph],
174                &[CGPoint::new(
175                    (subpixel_shift.x() / scale_factor) as CGFloat,
176                    (subpixel_shift.y() / scale_factor) as CGFloat,
177                )],
178            );
179
180            Some((bounds, pixels))
181        }
182    }
183
184    fn layout_str(
185        &self,
186        text: &str,
187        font_size: f32,
188        runs: &[(std::ops::Range<usize>, FontId)],
189    ) -> Line {
190        let font_id_attr_name = CFString::from_static_string("zed_font_id");
191
192        let mut string = CFMutableAttributedString::new();
193        string.replace_str(&CFString::new(text), CFRange::init(0, 0));
194
195        let mut utf16_lens = text.chars().map(|c| c.len_utf16());
196        let mut prev_char_ix = 0;
197        let mut prev_utf16_ix = 0;
198
199        for (range, font_id) in runs {
200            let utf16_start = prev_utf16_ix
201                + utf16_lens
202                    .by_ref()
203                    .take(range.start - prev_char_ix)
204                    .sum::<usize>();
205            let utf16_end = utf16_start
206                + utf16_lens
207                    .by_ref()
208                    .take(range.end - range.start)
209                    .sum::<usize>();
210            prev_char_ix = range.end;
211            prev_utf16_ix = utf16_end;
212
213            let cf_range = CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
214            let font = &self.fonts[font_id.0];
215            unsafe {
216                string.set_attribute(
217                    cf_range,
218                    kCTFontAttributeName,
219                    &font.native_font().clone_with_font_size(font_size as f64),
220                );
221                string.set_attribute(
222                    cf_range,
223                    font_id_attr_name.as_concrete_TypeRef(),
224                    &CFNumber::from(font_id.0 as i64),
225                );
226            }
227        }
228
229        let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
230
231        let mut utf16_chars = text.encode_utf16();
232        let mut char_ix = 0;
233        let mut prev_utf16_ix = 0;
234
235        let mut runs = Vec::new();
236        for run in line.glyph_runs().into_iter() {
237            let font_id = FontId(
238                run.attributes()
239                    .unwrap()
240                    .get(&font_id_attr_name)
241                    .downcast::<CFNumber>()
242                    .unwrap()
243                    .to_i64()
244                    .unwrap() as usize,
245            );
246
247            let mut glyphs = Vec::new();
248            for ((glyph_id, position), utf16_ix) in run
249                .glyphs()
250                .iter()
251                .zip(run.positions().iter())
252                .zip(run.string_indices().iter())
253            {
254                let utf16_ix = usize::try_from(*utf16_ix).unwrap();
255                char_ix +=
256                    char::decode_utf16(utf16_chars.by_ref().take(utf16_ix - prev_utf16_ix)).count();
257                prev_utf16_ix = utf16_ix;
258
259                glyphs.push(Glyph {
260                    id: *glyph_id as GlyphId,
261                    position: vec2f(position.x as f32, position.y as f32),
262                    index: char_ix,
263                });
264            }
265
266            runs.push(Run { font_id, glyphs })
267        }
268
269        let typographic_bounds = line.get_typographic_bounds();
270        Line {
271            width: typographic_bounds.width as f32,
272            ascent: typographic_bounds.ascent as f32,
273            descent: typographic_bounds.descent as f32,
274            runs,
275            font_size,
276            len: char_ix + 1,
277        }
278    }
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284    use font_kit::properties::{Style, Weight};
285    use platform::FontSystem as _;
286
287    #[test]
288    fn test_layout_str() -> anyhow::Result<()> {
289        let fonts = FontSystem::new();
290        let menlo = fonts.load_family("Menlo")?;
291        let menlo_regular = fonts.select_font(&menlo, &Properties::new())?;
292        let menlo_italic = fonts.select_font(&menlo, &Properties::new().style(Style::Italic))?;
293        let menlo_bold = fonts.select_font(&menlo, &Properties::new().weight(Weight::BOLD))?;
294
295        let line = fonts.layout_str(
296            "hello world",
297            16.0,
298            &[
299                (0..2, menlo_bold),
300                (2..6, menlo_italic),
301                (6..11, menlo_regular),
302            ],
303        );
304        assert_eq!(line.runs.len(), 3);
305        assert_eq!(line.runs[0].font_id, menlo_bold);
306        assert_eq!(line.runs[0].glyphs.len(), 2);
307        assert_eq!(line.runs[1].font_id, menlo_italic);
308        assert_eq!(line.runs[1].glyphs.len(), 4);
309        assert_eq!(line.runs[2].font_id, menlo_regular);
310        assert_eq!(line.runs[2].glyphs.len(), 5);
311        Ok(())
312    }
313
314    #[test]
315    fn test_char_indices() -> anyhow::Result<()> {
316        let fonts = FontSystem::new();
317        let zapfino = fonts.load_family("Zapfino")?;
318        let zapfino_regular = fonts.select_font(&zapfino, &Properties::new())?;
319        let menlo = fonts.load_family("Menlo")?;
320        let menlo_regular = fonts.select_font(&menlo, &Properties::new())?;
321
322        let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
323        let line = fonts.layout_str(
324            text,
325            16.0,
326            &[
327                (0..9, zapfino_regular),
328                (9..22, menlo_regular),
329                (22..text.encode_utf16().count(), zapfino_regular),
330            ],
331        );
332        assert_eq!(
333            line.runs
334                .iter()
335                .flat_map(|r| r.glyphs.iter())
336                .map(|g| g.index)
337                .collect::<Vec<_>>(),
338            vec![
339                0, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31
340            ]
341        );
342        Ok(())
343    }
344
345    // #[test]
346    // fn test_rasterize_glyph() {
347    //     use std::{fs::File, io::BufWriter, path::Path};
348
349    //     let fonts = FontSystem::new();
350    //     let font_ids = fonts.load_family("Fira Code").unwrap();
351    //     let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
352    //     let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
353
354    //     const VARIANTS: usize = 1;
355    //     for i in 0..VARIANTS {
356    //         let variant = i as f32 / VARIANTS as f32;
357    //         let (bounds, bytes) = fonts
358    //             .rasterize_glyph(font_id, 16.0, glyph_id, vec2f(variant, variant), 2.)
359    //             .unwrap();
360
361    //         let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
362    //         let path = Path::new(&name);
363    //         let file = File::create(path).unwrap();
364    //         let ref mut w = BufWriter::new(file);
365
366    //         let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
367    //         encoder.set_color(png::ColorType::Grayscale);
368    //         encoder.set_depth(png::BitDepth::Eight);
369    //         let mut writer = encoder.write_header().unwrap();
370    //         writer.write_image_data(&bytes).unwrap();
371    //     }
372    // }
373}