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