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