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// }