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