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