text_system.rs

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