text_system.rs

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