text_system.rs

  1use crate::{
  2    point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle,
  3    FontWeight, GlyphId, GlyphRasterizationParams, Pixels, PlatformTextSystem, Point, Result,
  4    ShapedGlyph, ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
  5};
  6use anyhow::anyhow;
  7use cocoa::appkit::{CGFloat, CGPoint};
  8use collections::HashMap;
  9use core_foundation::{
 10    array::CFIndex,
 11    attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
 12    base::{CFRange, TCFType},
 13    string::CFString,
 14};
 15use core_graphics::{base::CGGlyph, color_space::CGColorSpace, context::CGContext};
 16use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
 17use font_kit::{
 18    font::Font as FontKitFont,
 19    handle::Handle,
 20    hinting::HintingOptions,
 21    metrics::Metrics,
 22    properties::{Style as FontkitStyle, Weight as FontkitWeight},
 23    source::SystemSource,
 24    sources::mem::MemSource,
 25};
 26use parking_lot::{RwLock, RwLockUpgradableReadGuard};
 27use pathfinder_geometry::{
 28    rect::{RectF, RectI},
 29    transform2d::Transform2F,
 30    vector::{Vector2F, Vector2I},
 31};
 32use smallvec::SmallVec;
 33use std::{char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
 34
 35use super::open_type;
 36
 37#[allow(non_upper_case_globals)]
 38const kCGImageAlphaOnly: u32 = 7;
 39
 40pub struct MacTextSystem(RwLock<MacTextSystemState>);
 41
 42struct MacTextSystemState {
 43    memory_source: MemSource,
 44    system_source: SystemSource,
 45    fonts: Vec<FontKitFont>,
 46    font_selections: HashMap<Font, FontId>,
 47    font_ids_by_postscript_name: HashMap<String, FontId>,
 48    font_ids_by_family_name: HashMap<SharedString, SmallVec<[FontId; 4]>>,
 49    postscript_names_by_font_id: HashMap<FontId, String>,
 50}
 51
 52impl MacTextSystem {
 53    pub fn new() -> Self {
 54        Self(RwLock::new(MacTextSystemState {
 55            memory_source: MemSource::empty(),
 56            system_source: SystemSource::new(),
 57            fonts: Vec::new(),
 58            font_selections: HashMap::default(),
 59            font_ids_by_postscript_name: HashMap::default(),
 60            font_ids_by_family_name: HashMap::default(),
 61            postscript_names_by_font_id: HashMap::default(),
 62        }))
 63    }
 64}
 65
 66impl Default for MacTextSystem {
 67    fn default() -> Self {
 68        Self::new()
 69    }
 70}
 71
 72impl PlatformTextSystem for MacTextSystem {
 73    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
 74        self.0.write().add_fonts(fonts)
 75    }
 76
 77    fn all_font_families(&self) -> Vec<String> {
 78        self.0
 79            .read()
 80            .system_source
 81            .all_families()
 82            .expect("core text should never return an error")
 83    }
 84
 85    fn font_id(&self, font: &Font) -> Result<FontId> {
 86        let lock = self.0.upgradable_read();
 87        if let Some(font_id) = lock.font_selections.get(font) {
 88            Ok(*font_id)
 89        } else {
 90            let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
 91            let candidates = if let Some(font_ids) = lock.font_ids_by_family_name.get(&font.family)
 92            {
 93                font_ids.as_slice()
 94            } else {
 95                let font_ids = lock.load_family(&font.family, font.features)?;
 96                lock.font_ids_by_family_name
 97                    .insert(font.family.clone(), font_ids);
 98                lock.font_ids_by_family_name[&font.family].as_ref()
 99            };
100
101            let candidate_properties = candidates
102                .iter()
103                .map(|font_id| lock.fonts[font_id.0].properties())
104                .collect::<SmallVec<[_; 4]>>();
105
106            let ix = font_kit::matching::find_best_match(
107                &candidate_properties,
108                &font_kit::properties::Properties {
109                    style: font.style.into(),
110                    weight: font.weight.into(),
111                    stretch: Default::default(),
112                },
113            )?;
114
115            Ok(candidates[ix])
116        }
117    }
118
119    fn font_metrics(&self, font_id: FontId) -> FontMetrics {
120        self.0.read().fonts[font_id.0].metrics().into()
121    }
122
123    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
124        Ok(self.0.read().fonts[font_id.0]
125            .typographic_bounds(glyph_id.into())?
126            .into())
127    }
128
129    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
130        self.0.read().advance(font_id, glyph_id)
131    }
132
133    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
134        self.0.read().glyph_for_char(font_id, ch)
135    }
136
137    fn glyph_raster_bounds(
138        &self,
139        params: &GlyphRasterizationParams,
140    ) -> Result<Bounds<DevicePixels>> {
141        self.0.read().raster_bounds(params)
142    }
143
144    fn rasterize_glyph(
145        &self,
146        glyph_id: &GlyphRasterizationParams,
147    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
148        self.0.read().rasterize_glyph(glyph_id)
149    }
150
151    fn layout_line(
152        &self,
153        text: &str,
154        font_size: Pixels,
155        font_runs: &[(usize, FontId)],
156    ) -> ShapedLine {
157        self.0.write().layout_line(text, font_size, font_runs)
158    }
159
160    fn wrap_line(
161        &self,
162        text: &str,
163        font_id: FontId,
164        font_size: Pixels,
165        width: Pixels,
166    ) -> Vec<usize> {
167        self.0.read().wrap_line(text, font_id, font_size, width)
168    }
169}
170
171impl MacTextSystemState {
172    fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
173        self.memory_source.add_fonts(
174            fonts
175                .iter()
176                .map(|bytes| Handle::from_memory(bytes.clone(), 0)),
177        )?;
178        Ok(())
179    }
180
181    fn load_family(
182        &mut self,
183        name: &SharedString,
184        features: FontFeatures,
185    ) -> Result<SmallVec<[FontId; 4]>> {
186        let mut font_ids = SmallVec::new();
187        let family = self
188            .memory_source
189            .select_family_by_name(name.as_ref())
190            .or_else(|_| self.system_source.select_family_by_name(name.as_ref()))?;
191        for font in family.fonts() {
192            let mut font = font.load()?;
193            open_type::apply_features(&mut font, features);
194            let font_id = FontId(self.fonts.len());
195            font_ids.push(font_id);
196            let postscript_name = font.postscript_name().unwrap();
197            self.font_ids_by_postscript_name
198                .insert(postscript_name.clone(), font_id);
199            self.postscript_names_by_font_id
200                .insert(font_id, postscript_name);
201            self.fonts.push(font);
202        }
203        Ok(font_ids)
204    }
205
206    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
207        Ok(self.fonts[font_id.0].advance(glyph_id.into())?.into())
208    }
209
210    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
211        self.fonts[font_id.0].glyph_for_char(ch).map(Into::into)
212    }
213
214    fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {
215        let postscript_name = requested_font.postscript_name();
216        if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {
217            *font_id
218        } else {
219            let font_id = FontId(self.fonts.len());
220            self.font_ids_by_postscript_name
221                .insert(postscript_name.clone(), font_id);
222            self.postscript_names_by_font_id
223                .insert(font_id, postscript_name);
224            self.fonts
225                .push(font_kit::font::Font::from_core_graphics_font(
226                    requested_font.copy_to_CGFont(),
227                ));
228            font_id
229        }
230    }
231
232    fn is_emoji(&self, font_id: FontId) -> bool {
233        self.postscript_names_by_font_id
234            .get(&font_id)
235            .map_or(false, |postscript_name| {
236                postscript_name == "AppleColorEmoji"
237            })
238    }
239
240    fn raster_bounds(&self, params: &GlyphRasterizationParams) -> Result<Bounds<DevicePixels>> {
241        let font = &self.fonts[params.font_id.0];
242        let scale = Transform2F::from_scale(params.scale_factor);
243        Ok(font
244            .raster_bounds(
245                params.glyph_id.into(),
246                params.font_size.into(),
247                scale,
248                HintingOptions::None,
249                font_kit::canvas::RasterizationOptions::GrayscaleAa,
250            )?
251            .into())
252    }
253
254    fn rasterize_glyph(
255        &self,
256        params: &GlyphRasterizationParams,
257    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
258        let glyph_bounds = self.raster_bounds(params)?;
259        if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
260            Err(anyhow!("glyph bounds are empty"))
261        } else {
262            // Add an extra pixel when the subpixel variant isn't zero to make room for anti-aliasing.
263            let mut bitmap_size = glyph_bounds.size;
264            if params.subpixel_variant.x > 0 {
265                bitmap_size.width += DevicePixels(1);
266            }
267            if params.subpixel_variant.y > 0 {
268                bitmap_size.height += DevicePixels(1);
269            }
270
271            let mut bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
272            let cx = CGContext::create_bitmap_context(
273                Some(bytes.as_mut_ptr() as *mut _),
274                bitmap_size.width.0 as usize,
275                bitmap_size.height.0 as usize,
276                8,
277                bitmap_size.width.0 as usize,
278                &CGColorSpace::create_device_gray(),
279                kCGImageAlphaOnly,
280            );
281
282            // Move the origin to bottom left and account for scaling, this
283            // makes drawing text consistent with the font-kit's raster_bounds.
284            cx.translate(
285                -glyph_bounds.origin.x.0 as CGFloat,
286                (glyph_bounds.origin.y.0 + glyph_bounds.size.height.0) as CGFloat,
287            );
288            cx.scale(
289                params.scale_factor as CGFloat,
290                params.scale_factor as CGFloat,
291            );
292
293            let subpixel_shift = params
294                .subpixel_variant
295                .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32 / params.scale_factor);
296            cx.set_allows_font_subpixel_positioning(true);
297            cx.set_should_subpixel_position_fonts(true);
298            cx.set_allows_font_subpixel_quantization(false);
299            cx.set_should_subpixel_quantize_fonts(false);
300            self.fonts[params.font_id.0]
301                .native_font()
302                .clone_with_font_size(f32::from(params.font_size) as CGFloat)
303                .draw_glyphs(
304                    &[u32::from(params.glyph_id) as CGGlyph],
305                    &[CGPoint::new(
306                        subpixel_shift.x as CGFloat,
307                        subpixel_shift.y as CGFloat,
308                    )],
309                    cx,
310                );
311
312            Ok((bitmap_size.into(), bytes))
313        }
314    }
315
316    fn layout_line(
317        &mut self,
318        text: &str,
319        font_size: Pixels,
320        font_runs: &[(usize, FontId)],
321    ) -> ShapedLine {
322        // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
323        let mut string = CFMutableAttributedString::new();
324        {
325            string.replace_str(&CFString::new(text), CFRange::init(0, 0));
326            let utf16_line_len = string.char_len() as usize;
327
328            let mut ix_converter = StringIndexConverter::new(text);
329            for (run_len, font_id) in font_runs {
330                let utf8_end = ix_converter.utf8_ix + run_len;
331                let utf16_start = ix_converter.utf16_ix;
332
333                if utf16_start >= utf16_line_len {
334                    break;
335                }
336
337                ix_converter.advance_to_utf8_ix(utf8_end);
338                let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
339
340                let cf_range =
341                    CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
342
343                let font: &FontKitFont = &self.fonts[font_id.0];
344                unsafe {
345                    string.set_attribute(
346                        cf_range,
347                        kCTFontAttributeName,
348                        &font.native_font().clone_with_font_size(font_size.into()),
349                    );
350                }
351
352                if utf16_end == utf16_line_len {
353                    break;
354                }
355            }
356        }
357
358        // Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
359        let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
360
361        let mut runs = Vec::new();
362        for run in line.glyph_runs().into_iter() {
363            let attributes = run.attributes().unwrap();
364            let font = unsafe {
365                attributes
366                    .get(kCTFontAttributeName)
367                    .downcast::<CTFont>()
368                    .unwrap()
369            };
370            let font_id = self.id_for_native_font(font);
371
372            let mut ix_converter = StringIndexConverter::new(text);
373            let mut glyphs = Vec::new();
374            for ((glyph_id, position), glyph_utf16_ix) in run
375                .glyphs()
376                .iter()
377                .zip(run.positions().iter())
378                .zip(run.string_indices().iter())
379            {
380                let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
381                ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
382                glyphs.push(ShapedGlyph {
383                    id: (*glyph_id).into(),
384                    position: point(position.x as f32, position.y as f32).map(px),
385                    index: ix_converter.utf8_ix,
386                    is_emoji: self.is_emoji(font_id),
387                });
388            }
389
390            runs.push(ShapedRun { font_id, glyphs })
391        }
392
393        let typographic_bounds = line.get_typographic_bounds();
394        ShapedLine {
395            width: typographic_bounds.width.into(),
396            ascent: typographic_bounds.ascent.into(),
397            descent: typographic_bounds.descent.into(),
398            runs,
399            font_size,
400            len: text.len(),
401        }
402    }
403
404    fn wrap_line(
405        &self,
406        text: &str,
407        font_id: FontId,
408        font_size: Pixels,
409        width: Pixels,
410    ) -> Vec<usize> {
411        let mut string = CFMutableAttributedString::new();
412        string.replace_str(&CFString::new(text), CFRange::init(0, 0));
413        let cf_range = CFRange::init(0, text.encode_utf16().count() as isize);
414        let font = &self.fonts[font_id.0];
415        unsafe {
416            string.set_attribute(
417                cf_range,
418                kCTFontAttributeName,
419                &font.native_font().clone_with_font_size(font_size.into()),
420            );
421
422            let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
423            let mut ix_converter = StringIndexConverter::new(text);
424            let mut break_indices = Vec::new();
425            while ix_converter.utf8_ix < text.len() {
426                let utf16_len = CTTypesetterSuggestLineBreak(
427                    typesetter,
428                    ix_converter.utf16_ix as isize,
429                    width.into(),
430                ) as usize;
431                ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
432                if ix_converter.utf8_ix >= text.len() {
433                    break;
434                }
435                break_indices.push(ix_converter.utf8_ix as usize);
436            }
437            break_indices
438        }
439    }
440}
441
442#[derive(Clone)]
443struct StringIndexConverter<'a> {
444    text: &'a str,
445    utf8_ix: usize,
446    utf16_ix: usize,
447}
448
449impl<'a> StringIndexConverter<'a> {
450    fn new(text: &'a str) -> Self {
451        Self {
452            text,
453            utf8_ix: 0,
454            utf16_ix: 0,
455        }
456    }
457
458    fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
459        for (ix, c) in self.text[self.utf8_ix..].char_indices() {
460            if self.utf8_ix + ix >= utf8_target {
461                self.utf8_ix += ix;
462                return;
463            }
464            self.utf16_ix += c.len_utf16();
465        }
466        self.utf8_ix = self.text.len();
467    }
468
469    fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
470        for (ix, c) in self.text[self.utf8_ix..].char_indices() {
471            if self.utf16_ix >= utf16_target {
472                self.utf8_ix += ix;
473                return;
474            }
475            self.utf16_ix += c.len_utf16();
476        }
477        self.utf8_ix = self.text.len();
478    }
479}
480
481#[repr(C)]
482pub struct __CFTypesetter(c_void);
483
484pub type CTTypesetterRef = *const __CFTypesetter;
485
486#[link(name = "CoreText", kind = "framework")]
487extern "C" {
488    fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
489
490    fn CTTypesetterSuggestLineBreak(
491        typesetter: CTTypesetterRef,
492        start_index: CFIndex,
493        width: f64,
494    ) -> CFIndex;
495}
496
497impl From<Metrics> for FontMetrics {
498    fn from(metrics: Metrics) -> Self {
499        FontMetrics {
500            units_per_em: metrics.units_per_em,
501            ascent: metrics.ascent,
502            descent: metrics.descent,
503            line_gap: metrics.line_gap,
504            underline_position: metrics.underline_position,
505            underline_thickness: metrics.underline_thickness,
506            cap_height: metrics.cap_height,
507            x_height: metrics.x_height,
508            bounding_box: metrics.bounding_box.into(),
509        }
510    }
511}
512
513impl From<RectF> for Bounds<f32> {
514    fn from(rect: RectF) -> Self {
515        Bounds {
516            origin: point(rect.origin_x(), rect.origin_y()),
517            size: size(rect.width(), rect.height()),
518        }
519    }
520}
521
522impl From<RectI> for Bounds<DevicePixels> {
523    fn from(rect: RectI) -> Self {
524        Bounds {
525            origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
526            size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
527        }
528    }
529}
530
531impl From<Vector2I> for Size<DevicePixels> {
532    fn from(value: Vector2I) -> Self {
533        size(value.x().into(), value.y().into())
534    }
535}
536
537impl From<RectI> for Bounds<i32> {
538    fn from(rect: RectI) -> Self {
539        Bounds {
540            origin: point(rect.origin_x(), rect.origin_y()),
541            size: size(rect.width(), rect.height()),
542        }
543    }
544}
545
546impl From<Point<u32>> for Vector2I {
547    fn from(size: Point<u32>) -> Self {
548        Vector2I::new(size.x as i32, size.y as i32)
549    }
550}
551
552impl From<Vector2F> for Size<f32> {
553    fn from(vec: Vector2F) -> Self {
554        size(vec.x(), vec.y())
555    }
556}
557
558impl From<FontWeight> for FontkitWeight {
559    fn from(value: FontWeight) -> Self {
560        FontkitWeight(value.0)
561    }
562}
563
564impl From<FontStyle> for FontkitStyle {
565    fn from(style: FontStyle) -> Self {
566        match style {
567            FontStyle::Normal => FontkitStyle::Normal,
568            FontStyle::Italic => FontkitStyle::Italic,
569            FontStyle::Oblique => FontkitStyle::Oblique,
570        }
571    }
572}
573
574// #[cfg(test)]
575// mod tests {
576//     use super::*;
577//     use crate::AppContext;
578//     use font_kit::properties::{Style, Weight};
579//     use platform::FontSystem as _;
580
581//     #[crate::test(self, retries = 5)]
582//     fn test_layout_str(_: &mut AppContext) {
583//         // This is failing intermittently on CI and we don't have time to figure it out
584//         let fonts = FontSystem::new();
585//         let menlo = fonts.load_family("Menlo", &Default::default()).unwrap();
586//         let menlo_regular = RunStyle {
587//             font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
588//             color: Default::default(),
589//             underline: Default::default(),
590//         };
591//         let menlo_italic = RunStyle {
592//             font_id: fonts
593//                 .select_font(&menlo, Properties::new().style(Style::Italic))
594//                 .unwrap(),
595//             color: Default::default(),
596//             underline: Default::default(),
597//         };
598//         let menlo_bold = RunStyle {
599//             font_id: fonts
600//                 .select_font(&menlo, Properties::new().weight(Weight::BOLD))
601//                 .unwrap(),
602//             color: Default::default(),
603//             underline: Default::default(),
604//         };
605//         assert_ne!(menlo_regular, menlo_italic);
606//         assert_ne!(menlo_regular, menlo_bold);
607//         assert_ne!(menlo_italic, menlo_bold);
608
609//         let line = fonts.layout_line(
610//             "hello world",
611//             16.0,
612//             &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
613//         );
614//         assert_eq!(line.runs.len(), 3);
615//         assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
616//         assert_eq!(line.runs[0].glyphs.len(), 2);
617//         assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
618//         assert_eq!(line.runs[1].glyphs.len(), 4);
619//         assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
620//         assert_eq!(line.runs[2].glyphs.len(), 5);
621//     }
622
623//     #[test]
624//     fn test_glyph_offsets() -> crate::Result<()> {
625//         let fonts = FontSystem::new();
626//         let zapfino = fonts.load_family("Zapfino", &Default::default())?;
627//         let zapfino_regular = RunStyle {
628//             font_id: fonts.select_font(&zapfino, &Properties::new())?,
629//             color: Default::default(),
630//             underline: Default::default(),
631//         };
632//         let menlo = fonts.load_family("Menlo", &Default::default())?;
633//         let menlo_regular = RunStyle {
634//             font_id: fonts.select_font(&menlo, &Properties::new())?,
635//             color: Default::default(),
636//             underline: Default::default(),
637//         };
638
639//         let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
640//         let line = fonts.layout_line(
641//             text,
642//             16.0,
643//             &[
644//                 (9, zapfino_regular),
645//                 (13, menlo_regular),
646//                 (text.len() - 22, zapfino_regular),
647//             ],
648//         );
649//         assert_eq!(
650//             line.runs
651//                 .iter()
652//                 .flat_map(|r| r.glyphs.iter())
653//                 .map(|g| g.index)
654//                 .collect::<Vec<_>>(),
655//             vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
656//         );
657//         Ok(())
658//     }
659
660//     #[test]
661//     #[ignore]
662//     fn test_rasterize_glyph() {
663//         use std::{fs::File, io::BufWriter, path::Path};
664
665//         let fonts = FontSystem::new();
666//         let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap();
667//         let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
668//         let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
669
670//         const VARIANTS: usize = 1;
671//         for i in 0..VARIANTS {
672//             let variant = i as f32 / VARIANTS as f32;
673//             let (bounds, bytes) = fonts
674//                 .rasterize_glyph(
675//                     font_id,
676//                     16.0,
677//                     glyph_id,
678//                     vec2f(variant, variant),
679//                     2.,
680//                     RasterizationOptions::Alpha,
681//                 )
682//                 .unwrap();
683
684//             let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
685//             let path = Path::new(&name);
686//             let file = File::create(path).unwrap();
687//             let w = &mut BufWriter::new(file);
688
689//             let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
690//             encoder.set_color(png::ColorType::Grayscale);
691//             encoder.set_depth(png::BitDepth::Eight);
692//             let mut writer = encoder.write_header().unwrap();
693//             writer.write_image_data(&bytes).unwrap();
694//         }
695//     }
696
697//     #[test]
698//     fn test_wrap_line() {
699//         let fonts = FontSystem::new();
700//         let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
701//         let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
702
703//         let line = "one two three four five\n";
704//         let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
705//         assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
706
707//         let line = "aaa Ξ±Ξ±Ξ± βœ‹βœ‹βœ‹ πŸŽ‰πŸŽ‰πŸŽ‰\n";
708//         let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
709//         assert_eq!(
710//             wrap_boundaries,
711//             &["aaa Ξ±Ξ±Ξ± ".len(), "aaa Ξ±Ξ±Ξ± βœ‹βœ‹βœ‹ ".len(),]
712//         );
713//     }
714
715//     #[test]
716//     fn test_layout_line_bom_char() {
717//         let fonts = FontSystem::new();
718//         let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
719//         let style = RunStyle {
720//             font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
721//             color: Default::default(),
722//             underline: Default::default(),
723//         };
724
725//         let line = "\u{feff}";
726//         let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
727//         assert_eq!(layout.len, line.len());
728//         assert!(layout.runs.is_empty());
729
730//         let line = "a\u{feff}b";
731//         let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
732//         assert_eq!(layout.len, line.len());
733//         assert_eq!(layout.runs.len(), 1);
734//         assert_eq!(layout.runs[0].glyphs.len(), 2);
735//         assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
736//                                                      // There's no glyph for \u{feff}
737//         assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
738//     }
739// }