fonts.rs

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