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