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