font.rs (38587B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use api::{ColorF, ColorU, FontKey, FontRenderMode, FontSize, GlyphDimensions}; 6 use api::{FontInstanceFlags, FontVariation, NativeFontHandle}; 7 use core_foundation::data::CFData; 8 use core_foundation::base::TCFType; 9 use core_foundation::dictionary::CFDictionary; 10 use core_foundation::number::{CFNumber}; 11 use core_foundation::string::CFString; 12 use core_foundation::url::{CFURL, kCFURLPOSIXPathStyle}; 13 use core_graphics::base::{kCGImageAlphaNoneSkipFirst, kCGImageAlphaPremultipliedFirst}; 14 use core_graphics::base::{kCGBitmapByteOrder32Little}; 15 use core_graphics::color_space::CGColorSpace; 16 use core_graphics::context::CGContext; 17 use core_graphics::context::{CGBlendMode, CGTextDrawingMode}; 18 use core_graphics::font::{CGFont, CGGlyph}; 19 use core_graphics::geometry::{CGAffineTransform, CGPoint, CGSize}; 20 use core_graphics::geometry::{CG_AFFINE_TRANSFORM_IDENTITY, CGRect}; 21 use core_text::font::CTFont; 22 use core_text::font_descriptor::{CTFontDescriptor, kCTFontDefaultOrientation}; 23 use core_text::font_descriptor::{kCTFontURLAttribute, kCTFontVariationAttribute}; 24 use core_text::font_manager; 25 use euclid::default::Size2D; 26 use crate::gamma_lut::{ColorLut, GammaLut}; 27 use crate::rasterizer::{FontInstance, FontTransform, GlyphKey}; 28 use crate::rasterizer::{GlyphFormat, GlyphRasterError, GlyphRasterResult, RasterizedGlyph}; 29 use crate::types::FastHashMap; 30 use std::collections::hash_map::Entry; 31 use std::sync::Arc; 32 33 const INITIAL_CG_CONTEXT_SIDE_LENGTH: u32 = 32; 34 35 pub struct FontContext { 36 ct_font_descs: FastHashMap<FontKey, CTFontDescriptor>, 37 // Table mapping a sized font key with variations to its instantiated CoreText font. 38 ct_fonts: FastHashMap<(FontKey, FontSize, Vec<FontVariation>), CTFont>, 39 #[allow(dead_code)] 40 graphics_context: GraphicsContext, 41 #[allow(dead_code)] 42 gamma_lut: GammaLut, 43 } 44 45 // core text is safe to use on multiple threads and non-shareable resources are 46 // all hidden inside their font context. 47 unsafe impl Send for FontContext {} 48 49 struct GlyphMetrics { 50 rasterized_left: i32, 51 #[allow(dead_code)] 52 rasterized_descent: i32, 53 rasterized_ascent: i32, 54 rasterized_width: i32, 55 rasterized_height: i32, 56 advance: f32, 57 } 58 59 // There are a number of different OS prefs that control whether or not 60 // requesting font smoothing actually results in subpixel AA. This gets even 61 // murkier in newer macOS versions that deprecate subpixel AA, with the prefs 62 // potentially interacting and overriding each other. In an attempt to future- 63 // proof things against any new prefs or interpretation of those prefs in 64 // future macOS versions, we do a check here to request font smoothing and see 65 // what result it actually gives us much like Skia does. We need to check for 66 // each of three potential results and process them in the font backend in 67 // distinct ways: 68 // 1) subpixel AA (differing RGB channels) with dilation 69 // 2) grayscale AA (matching RGB channels) with dilation, a compatibility mode 70 // 3) grayscale AA without dilation as if font smoothing was not requested 71 // We can discern between case 1 and the rest by checking if the subpixels differ. 72 // We can discern between cases 2 and 3 by rendering with and without smoothing 73 // and comparing the two to determine if there was some dilation. 74 // This returns the actual FontRenderMode needed to support each case, if any. 75 fn determine_font_smoothing_mode() -> Option<FontRenderMode> { 76 let mut smooth_context = CGContext::create_bitmap_context( 77 None, 78 12, 79 12, 80 8, 81 12 * 4, 82 &CGColorSpace::create_device_rgb(), 83 kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, 84 ); 85 smooth_context.set_should_smooth_fonts(true); 86 smooth_context.set_should_antialias(true); 87 smooth_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0); 88 let mut gray_context = CGContext::create_bitmap_context( 89 None, 90 12, 91 12, 92 8, 93 12 * 4, 94 &CGColorSpace::create_device_rgb(), 95 kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, 96 ); 97 gray_context.set_should_smooth_fonts(false); 98 gray_context.set_should_antialias(true); 99 gray_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0); 100 101 // Autorelease pool for CTFont 102 objc::rc::autoreleasepool(|| { 103 // Lucida Grande 12 is the default fallback font in Firefox 104 let ct_font = core_text::font::new_from_name("Lucida Grande", 12.).unwrap(); 105 let point = CGPoint { x: 0., y: 0. }; 106 let glyph = 'X' as CGGlyph; 107 ct_font.draw_glyphs(&[glyph], &[point], smooth_context.clone()); 108 ct_font.draw_glyphs(&[glyph], &[point], gray_context.clone()); 109 }); 110 111 let mut mode = None; 112 for (smooth, gray) in smooth_context.data().chunks(4).zip(gray_context.data().chunks(4)) { 113 if smooth[0] != smooth[1] || smooth[1] != smooth[2] { 114 return Some(FontRenderMode::Subpixel); 115 } 116 if smooth[0] != gray[0] || smooth[1] != gray[1] || smooth[2] != gray[2] { 117 mode = Some(FontRenderMode::Alpha); 118 } 119 } 120 return mode; 121 } 122 123 // We cache the font smoothing mode globally, rather than storing it in each FontContext, 124 // to avoid having to determine this redundantly in each context and to avoid needing to 125 // lock them to access this setting in prepare_font. 126 lazy_static! { 127 static ref FONT_SMOOTHING_MODE: Option<FontRenderMode> = determine_font_smoothing_mode(); 128 } 129 130 fn get_glyph_metrics( 131 ct_font: &CTFont, 132 transform: Option<&CGAffineTransform>, 133 glyph: CGGlyph, 134 x_offset: f64, 135 y_offset: f64, 136 extra_width: f64, 137 ) -> GlyphMetrics { 138 let mut bounds = ct_font.get_bounding_rects_for_glyphs(kCTFontDefaultOrientation, &[glyph]); 139 140 if bounds.origin.x.is_nan() || bounds.origin.y.is_nan() || bounds.size.width.is_nan() || 141 bounds.size.height.is_nan() 142 { 143 // If an unexpected glyph index is requested, core text will return NaN values 144 // which causes us to do bad thing as the value is cast into an integer and 145 // overflow when expanding the bounds a few lines below. 146 // Instead we are better off returning zero-sized metrics because this special 147 // case is handled by the callers of this method. 148 return GlyphMetrics { 149 rasterized_left: 0, 150 rasterized_width: 0, 151 rasterized_height: 0, 152 rasterized_ascent: 0, 153 rasterized_descent: 0, 154 advance: 0.0, 155 }; 156 } 157 158 let mut advance = CGSize { width: 0.0, height: 0.0 }; 159 unsafe { 160 ct_font.get_advances_for_glyphs(kCTFontDefaultOrientation, &glyph, &mut advance, 1); 161 } 162 163 if bounds.size.width > 0.0 { 164 bounds.size.width += extra_width; 165 } 166 if advance.width > 0.0 { 167 advance.width += extra_width; 168 } 169 170 if let Some(transform) = transform { 171 bounds = bounds.apply_transform(transform); 172 } 173 174 // First round out to pixel boundaries 175 // CG Origin is bottom left 176 let mut left = bounds.origin.x.floor() as i32; 177 let mut bottom = bounds.origin.y.floor() as i32; 178 let mut right = (bounds.origin.x + bounds.size.width + x_offset).ceil() as i32; 179 let mut top = (bounds.origin.y + bounds.size.height + y_offset).ceil() as i32; 180 181 // Expand the bounds by 1 pixel, to give CG room for anti-aliasing. 182 // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset 183 // is not currently known, as CG dilates the outlines by some percentage. 184 // This is taken from Skia. 185 left -= 1; 186 bottom -= 1; 187 right += 1; 188 top += 1; 189 190 let width = right - left; 191 let height = top - bottom; 192 193 GlyphMetrics { 194 rasterized_left: left, 195 rasterized_width: width, 196 rasterized_height: height, 197 rasterized_ascent: top, 198 rasterized_descent: -bottom, 199 advance: advance.width as f32, 200 } 201 } 202 203 fn new_ct_font_with_variations(ct_font_desc: &CTFontDescriptor, size: f64, variations: &[FontVariation]) -> CTFont { 204 let ct_font = core_text::font::new_from_descriptor(ct_font_desc, size); 205 if variations.is_empty() { 206 return ct_font; 207 } 208 let mut vals: Vec<(CFNumber, CFNumber)> = Vec::with_capacity(variations.len() as usize); 209 for variation in variations { 210 vals.push((CFNumber::from(variation.tag as i64), CFNumber::from(variation.value as f64))); 211 } 212 if vals.is_empty() { 213 return ct_font; 214 } 215 let vals_dict = CFDictionary::from_CFType_pairs(&vals); 216 let variation_attribute = unsafe { CFString::wrap_under_get_rule(kCTFontVariationAttribute) }; 217 let attrs_dict = CFDictionary::from_CFType_pairs(&[(variation_attribute, vals_dict)]); 218 let ct_var_font_desc = ct_font.copy_descriptor().create_copy_with_attributes(attrs_dict.to_untyped()).unwrap(); 219 core_text::font::new_from_descriptor(&ct_var_font_desc, size) 220 221 } 222 223 // We rely on Gecko to determine whether the font may have color glyphs to avoid 224 // needing to load the font ahead of time to query its symbolic traits. 225 fn is_bitmap_font(font: &FontInstance) -> bool { 226 font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS) 227 } 228 229 impl FontContext { 230 pub fn distribute_across_threads() -> bool { 231 true 232 } 233 234 pub fn new() -> FontContext { 235 debug!("Test for subpixel AA support: {:?}", *FONT_SMOOTHING_MODE); 236 237 // Force CG to use sRGB color space to gamma correct. 238 let contrast = 0.0; 239 let gamma = 0.0; 240 241 FontContext { 242 ct_font_descs: FastHashMap::default(), 243 ct_fonts: FastHashMap::default(), 244 graphics_context: GraphicsContext::new(), 245 gamma_lut: GammaLut::new(contrast, gamma, gamma), 246 } 247 } 248 249 pub fn add_raw_font(&mut self, font_key: &FontKey, bytes: Arc<Vec<u8>>, index: u32) { 250 if self.ct_font_descs.contains_key(font_key) { 251 return; 252 } 253 254 assert_eq!(index, 0); 255 let data = CFData::from_arc(bytes); 256 let ct_font_desc = match font_manager::create_font_descriptor_with_data(data) { 257 Err(_) => return, 258 Ok(cg_font) => cg_font, 259 }; 260 self.ct_font_descs.insert(*font_key, ct_font_desc); 261 } 262 263 pub fn add_native_font(&mut self, font_key: &FontKey, native_font_handle: NativeFontHandle) { 264 if self.ct_font_descs.contains_key(font_key) { 265 return; 266 } 267 268 // There's no way great way to go from a CGFont to a CTFontDescriptor 269 // We could use the postscript name but that doesn't work for the 270 // system UI fonts on newer macOS versions. Instead we create a CTFont 271 // and use the descriptor for that. Normally we'd try to avoid new_from_CGFont 272 // because that adds the CGFont to the descriptor cache which can keep the CGFont 273 // around for a long time, but that should be ok for non-web (native) fonts. 274 let cf_name = CFString::new(&native_font_handle.name); 275 276 // For "hidden" system fonts, whose names start with a period, 277 // we can't instantiate CTFonts via a descriptor. We're really 278 // supposed to use CTFontCreateUIFontForLanguage, but for now 279 // we just use the CGFont. 280 let mut desc = if native_font_handle.name.starts_with('.') { 281 let cg_font = match CGFont::from_name(&cf_name) { 282 Ok(cg_font) => cg_font, 283 Err(_) => { 284 // If for some reason we failed to load a font descriptor, then our 285 // only options are to either abort or substitute a fallback font. 286 // It is preferable to use a fallback font instead so that rendering 287 // can at least still proceed in some fashion without erroring. 288 // Lucida Grande is the fallback font in Gecko, so use that here. 289 CGFont::from_name(&CFString::from_static_string("Lucida Grande")) 290 .expect("couldn't find font with postscript name and couldn't load fallback font") 291 } 292 }; 293 core_text::font::new_from_CGFont(&cg_font, 0.).copy_descriptor() 294 } else { 295 core_text::font_descriptor::new_from_postscript_name(&cf_name) 296 }; 297 298 // If the NativeFontHandle includes a file path, add this to the descriptor 299 // to disambiguate cases where multiple installed fonts have the same psname. 300 if native_font_handle.path.len() > 0 { 301 let cf_path = CFString::new(&native_font_handle.path); 302 let url_attribute = unsafe { CFString::wrap_under_get_rule(kCTFontURLAttribute) }; 303 let attrs = CFDictionary::from_CFType_pairs(&[ 304 (url_attribute, CFURL::from_file_system_path(cf_path, kCFURLPOSIXPathStyle, false)), 305 ]); 306 if let Ok(desc_with_path) = desc.create_copy_with_attributes(attrs.to_untyped()) { 307 desc = desc_with_path; 308 } 309 } 310 311 self.ct_font_descs 312 .insert(*font_key, desc); 313 } 314 315 pub fn delete_font(&mut self, font_key: &FontKey) { 316 if let Some(_) = self.ct_font_descs.remove(font_key) { 317 self.ct_fonts.retain(|k, _| k.0 != *font_key); 318 } 319 } 320 321 pub fn delete_font_instance(&mut self, instance: &FontInstance) { 322 // Remove the CoreText font corresponding to this instance. 323 let size = FontSize::from_f64_px(instance.get_transformed_size()); 324 self.ct_fonts.remove(&(instance.font_key, size, instance.variations.clone())); 325 } 326 327 fn get_ct_font( 328 &mut self, 329 font_key: FontKey, 330 size: f64, 331 variations: &[FontVariation], 332 ) -> Option<CTFont> { 333 // Interacting with CoreText can create autorelease garbage. 334 objc::rc::autoreleasepool(|| { 335 match self.ct_fonts.entry((font_key, FontSize::from_f64_px(size), variations.to_vec())) { 336 Entry::Occupied(entry) => Some((*entry.get()).clone()), 337 Entry::Vacant(entry) => { 338 let ct_font_desc = self.ct_font_descs.get(&font_key)?; 339 let ct_font = new_ct_font_with_variations(ct_font_desc, size, variations); 340 entry.insert(ct_font.clone()); 341 Some(ct_font) 342 } 343 } 344 }) 345 } 346 347 pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> { 348 let character = ch as u16; 349 let mut glyph = 0; 350 351 self.get_ct_font(font_key, 16.0, &[]) 352 .and_then(|ct_font| { 353 unsafe { 354 let result = ct_font.get_glyphs_for_characters(&character, &mut glyph, 1); 355 356 if result { 357 Some(glyph as u32) 358 } else { 359 None 360 } 361 } 362 }) 363 } 364 365 pub fn get_glyph_dimensions( 366 &mut self, 367 font: &FontInstance, 368 key: &GlyphKey, 369 ) -> Option<GlyphDimensions> { 370 let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); 371 let size = font.size.to_f64_px() * y_scale; 372 self.get_ct_font(font.font_key, size, &font.variations) 373 .and_then(|ct_font| { 374 let glyph = key.index() as CGGlyph; 375 let bitmap = is_bitmap_font(font); 376 let (mut shape, (x_offset, y_offset)) = if bitmap { 377 (FontTransform::identity(), (0.0, 0.0)) 378 } else { 379 (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key)) 380 }; 381 if font.flags.contains(FontInstanceFlags::FLIP_X) { 382 shape = shape.flip_x(); 383 } 384 if font.flags.contains(FontInstanceFlags::FLIP_Y) { 385 shape = shape.flip_y(); 386 } 387 if font.flags.contains(FontInstanceFlags::TRANSPOSE) { 388 shape = shape.swap_xy(); 389 } 390 let (mut tx, mut ty) = (0.0, 0.0); 391 if font.synthetic_italics.is_enabled() { 392 let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, size); 393 shape = shape_; 394 tx = tx_; 395 ty = ty_; 396 } 397 let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) { 398 Some(CGAffineTransform { 399 a: shape.scale_x as f64, 400 b: -shape.skew_y as f64, 401 c: -shape.skew_x as f64, 402 d: shape.scale_y as f64, 403 tx: tx, 404 ty: -ty, 405 }) 406 } else { 407 None 408 }; 409 let (strike_scale, pixel_step) = if bitmap { 410 (y_scale, 1.0) 411 } else { 412 (x_scale, y_scale / x_scale) 413 }; 414 let extra_strikes = font.get_extra_strikes( 415 FontInstanceFlags::SYNTHETIC_BOLD | FontInstanceFlags::MULTISTRIKE_BOLD, 416 strike_scale, 417 ); 418 let metrics = get_glyph_metrics( 419 &ct_font, 420 transform.as_ref(), 421 glyph, 422 x_offset, 423 y_offset, 424 extra_strikes as f64 * pixel_step, 425 ); 426 if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 { 427 None 428 } else { 429 Some(GlyphDimensions { 430 left: metrics.rasterized_left, 431 top: metrics.rasterized_ascent, 432 width: metrics.rasterized_width, 433 height: metrics.rasterized_height, 434 advance: metrics.advance, 435 }) 436 } 437 }) 438 } 439 440 // Assumes the pixels here are linear values from CG 441 fn gamma_correct_pixels( 442 &self, 443 pixels: &mut Vec<u8>, 444 render_mode: FontRenderMode, 445 color: ColorU, 446 ) { 447 let ColorU {r, g, b, a} = color; 448 let smooth_color = match *FONT_SMOOTHING_MODE { 449 // Use Skia's gamma approximation for subpixel smoothing of 3/4. 450 Some(FontRenderMode::Subpixel) => ColorU::new(r - r / 4, g - g / 4, b - b / 4, a), 451 // Use Skia's gamma approximation for grayscale smoothing of 1/2. 452 Some(FontRenderMode::Alpha) => ColorU::new(r / 2, g / 2, b / 2, a), 453 _ => color, 454 }; 455 456 // Then convert back to gamma corrected values. 457 match render_mode { 458 FontRenderMode::Alpha => { 459 self.gamma_lut.preblend_grayscale(pixels, smooth_color); 460 } 461 FontRenderMode::Subpixel => { 462 self.gamma_lut.preblend(pixels, smooth_color); 463 } 464 _ => {} // Again, give mono untouched since only the alpha matters. 465 } 466 } 467 468 #[allow(dead_code)] 469 fn print_glyph_data(&mut self, data: &[u8], width: usize, height: usize) { 470 // Rust doesn't have step_by support on stable :( 471 debug!("Width is: {:?} height: {:?}", width, height); 472 for i in 0 .. height { 473 let current_height = i * width * 4; 474 475 for pixel in data[current_height .. current_height + (width * 4)].chunks(4) { 476 let b = pixel[0]; 477 let g = pixel[1]; 478 let r = pixel[2]; 479 let a = pixel[3]; 480 debug!("({}, {}, {}, {}) ", r, g, b, a); 481 } 482 } 483 } 484 485 pub fn prepare_font(font: &mut FontInstance) { 486 if is_bitmap_font(font) { 487 // Render mode is ignored for bitmap fonts. Also, avoid normalizing the color 488 // in case CoreText needs the current color for rendering glyph color layers. 489 font.render_mode = FontRenderMode::Mono; 490 font.disable_subpixel_position(); 491 return; 492 } 493 // Sanitize the render mode for font smoothing. If font smoothing is supported, 494 // then we just need to ensure the render mode is limited to what is supported. 495 // If font smoothing is actually disabled, then we need to fall back to grayscale. 496 if font.flags.contains(FontInstanceFlags::FONT_SMOOTHING) || 497 font.render_mode == FontRenderMode::Subpixel { 498 match *FONT_SMOOTHING_MODE { 499 Some(mode) => { 500 font.render_mode = font.render_mode.limit_by(mode); 501 font.flags.insert(FontInstanceFlags::FONT_SMOOTHING); 502 } 503 None => { 504 font.render_mode = font.render_mode.limit_by(FontRenderMode::Alpha); 505 font.flags.remove(FontInstanceFlags::FONT_SMOOTHING); 506 } 507 } 508 } 509 match font.render_mode { 510 FontRenderMode::Mono => { 511 // In mono mode the color of the font is irrelevant. 512 font.color = ColorU::new(255, 255, 255, 255); 513 // Subpixel positioning is disabled in mono mode. 514 font.disable_subpixel_position(); 515 } 516 FontRenderMode::Alpha => { 517 font.color = if font.flags.contains(FontInstanceFlags::FONT_SMOOTHING) { 518 font.color.luminance_color().quantize() 519 } else { 520 ColorU::new(255, 255, 255, 255) 521 }; 522 } 523 FontRenderMode::Subpixel => { 524 font.color = font.color.quantize(); 525 } 526 } 527 } 528 529 pub fn begin_rasterize(_font: &FontInstance) { 530 } 531 532 pub fn end_rasterize(_font: &FontInstance) { 533 } 534 535 pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult { 536 objc::rc::autoreleasepool(|| { 537 let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0)); 538 let size = font.size.to_f64_px() * y_scale; 539 let ct_font = 540 self.get_ct_font(font.font_key, size, &font.variations).ok_or(GlyphRasterError::LoadFailed)?; 541 let glyph_type = if is_bitmap_font(font) { 542 GlyphType::Bitmap 543 } else { 544 GlyphType::Vector 545 }; 546 547 let (mut shape, (x_offset, y_offset)) = match glyph_type { 548 GlyphType::Bitmap => (FontTransform::identity(), (0.0, 0.0)), 549 GlyphType::Vector => { 550 (font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key)) 551 } 552 }; 553 if font.flags.contains(FontInstanceFlags::FLIP_X) { 554 shape = shape.flip_x(); 555 } 556 if font.flags.contains(FontInstanceFlags::FLIP_Y) { 557 shape = shape.flip_y(); 558 } 559 if font.flags.contains(FontInstanceFlags::TRANSPOSE) { 560 shape = shape.swap_xy(); 561 } 562 let (mut tx, mut ty) = (0.0, 0.0); 563 if font.synthetic_italics.is_enabled() { 564 let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, size); 565 shape = shape_; 566 tx = tx_; 567 ty = ty_; 568 } 569 let transform = if !shape.is_identity() || (tx, ty) != (0.0, 0.0) { 570 Some(CGAffineTransform { 571 a: shape.scale_x as f64, 572 b: -shape.skew_y as f64, 573 c: -shape.skew_x as f64, 574 d: shape.scale_y as f64, 575 tx: tx, 576 ty: -ty, 577 }) 578 } else { 579 None 580 }; 581 582 let glyph = key.index() as CGGlyph; 583 let (strike_scale, pixel_step) = if glyph_type == GlyphType::Bitmap { 584 (y_scale, 1.0) 585 } else { 586 (x_scale, y_scale / x_scale) 587 }; 588 let extra_strikes = font.get_extra_strikes( 589 FontInstanceFlags::SYNTHETIC_BOLD | FontInstanceFlags::MULTISTRIKE_BOLD, 590 strike_scale, 591 ); 592 let metrics = get_glyph_metrics( 593 &ct_font, 594 transform.as_ref(), 595 glyph, 596 x_offset, 597 y_offset, 598 extra_strikes as f64 * pixel_step, 599 ); 600 if metrics.rasterized_width == 0 || metrics.rasterized_height == 0 { 601 return Err(GlyphRasterError::LoadFailed); 602 } 603 604 let raster_size = Size2D::new( 605 metrics.rasterized_width as u32, 606 metrics.rasterized_height as u32 607 ); 608 609 // If the font render mode is Alpha, we support two different ways to 610 // compute the grayscale mask, depending on the value of the platform 611 // options' font_smoothing flag: 612 // - Alpha + smoothing: 613 // We will recover a grayscale mask from a subpixel rasterization, in 614 // such a way that the result looks as close to subpixel text 615 // blending as we can make it. This involves gamma correction, 616 // luminance computations and preblending based on the text color, 617 // just like with the Subpixel render mode. 618 // - Alpha without smoothing: 619 // We will ask CoreGraphics to rasterize the text with font_smoothing 620 // off. This will cause it to use grayscale anti-aliasing with 621 // comparatively thin text. This method of text rendering is not 622 // gamma-aware. 623 // 624 // For subpixel rasterization, starting with macOS 10.11, CoreGraphics 625 // uses different glyph dilation based on the text color. Bright text 626 // uses less font dilation (looks thinner) than dark text. 627 // As a consequence, when we ask CG to rasterize with subpixel AA, we 628 // will render white-on-black text as opposed to black-on-white text if 629 // the text color brightness exceeds a certain threshold. This applies 630 // to both the Subpixel and the "Alpha + smoothing" modes, but not to 631 // the "Alpha without smoothing" and Mono modes. 632 // 633 // Fonts with color glyphs may, depending on the state within per-glyph 634 // table data, require the current font color to determine the output 635 // color. For such fonts we must thus supply the current font color just 636 // in case it is necessary. 637 let use_font_smoothing = font.flags.contains(FontInstanceFlags::FONT_SMOOTHING); 638 let (antialias, smooth, text_color, bg_color) = match glyph_type { 639 GlyphType::Bitmap => (true, false, ColorF::from(font.color), ColorF::TRANSPARENT), 640 GlyphType::Vector => { 641 match (font.render_mode, use_font_smoothing) { 642 (FontRenderMode::Subpixel, _) | 643 (FontRenderMode::Alpha, true) => (true, true, ColorF::BLACK, ColorF::WHITE), 644 (FontRenderMode::Alpha, false) => (true, false, ColorF::BLACK, ColorF::WHITE), 645 (FontRenderMode::Mono, _) => (false, false, ColorF::BLACK, ColorF::WHITE), 646 } 647 } 648 }; 649 650 { 651 let cg_context = self.graphics_context.get_context(&raster_size, glyph_type); 652 653 // These are always true in Gecko, even for non-AA fonts 654 cg_context.set_allows_font_subpixel_positioning(true); 655 cg_context.set_should_subpixel_position_fonts(true); 656 657 // Don't quantize because we're doing it already. 658 cg_context.set_allows_font_subpixel_quantization(false); 659 cg_context.set_should_subpixel_quantize_fonts(false); 660 661 cg_context.set_should_smooth_fonts(smooth); 662 cg_context.set_should_antialias(antialias); 663 664 // Fill the background. This could be opaque white, opaque black, or 665 // transparency. 666 cg_context.set_rgb_fill_color( 667 bg_color.r.into(), 668 bg_color.g.into(), 669 bg_color.b.into(), 670 bg_color.a.into(), 671 ); 672 let rect = CGRect { 673 origin: CGPoint { x: 0.0, y: 0.0 }, 674 size: CGSize { 675 width: metrics.rasterized_width as f64, 676 height: metrics.rasterized_height as f64, 677 }, 678 }; 679 680 // Make sure we use the Copy blend mode, or else we'll get the Porter-Duff OVER 681 // operator, which can't clear to the transparent color! 682 cg_context.set_blend_mode(CGBlendMode::Copy); 683 cg_context.fill_rect(rect); 684 cg_context.set_blend_mode(CGBlendMode::Normal); 685 686 // Set the text color and draw the glyphs. 687 cg_context.set_rgb_fill_color( 688 text_color.r.into(), 689 text_color.g.into(), 690 text_color.b.into(), 691 1.0, 692 ); 693 cg_context.set_text_drawing_mode(CGTextDrawingMode::CGTextFill); 694 695 // CG Origin is bottom left, WR is top left. Need -y offset 696 let mut draw_origin = CGPoint { 697 x: -metrics.rasterized_left as f64 + x_offset + tx, 698 y: metrics.rasterized_descent as f64 - y_offset - ty, 699 }; 700 701 if let Some(transform) = transform { 702 cg_context.set_text_matrix(&transform); 703 704 draw_origin = draw_origin.apply_transform(&transform.invert()); 705 } else { 706 // Make sure to reset this because some previous glyph rasterization might have 707 // changed it. 708 cg_context.set_text_matrix(&CG_AFFINE_TRANSFORM_IDENTITY); 709 } 710 711 ct_font.draw_glyphs(&[glyph], &[draw_origin], cg_context.clone()); 712 713 // We'd like to render all the strikes in a single ct_font.draw_glyphs call, 714 // passing an array of glyph IDs and an array of origins, but unfortunately 715 // with some fonts, Core Text may inappropriately pixel-snap the rasterization, 716 // such that the strikes overprint instead of being offset. Rendering the 717 // strikes with individual draw_glyphs calls avoids this. 718 // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1633397 for details.) 719 for i in 1 ..= extra_strikes { 720 let origin = CGPoint { 721 x: draw_origin.x + i as f64 * pixel_step, 722 y: draw_origin.y, 723 }; 724 ct_font.draw_glyphs(&[glyph], &[origin], cg_context.clone()); 725 } 726 } 727 728 let mut rasterized_pixels = self.graphics_context 729 .get_rasterized_pixels(&raster_size, glyph_type); 730 731 if glyph_type == GlyphType::Vector { 732 // We rendered text into an opaque surface. The code below needs to 733 // ignore the current value of each pixel's alpha channel. But it's 734 // allowed to write to the alpha channel, because we're done calling 735 // CG functions now. 736 737 if smooth { 738 // Convert to linear space for subpixel AA. 739 // We explicitly do not do this for grayscale AA ("Alpha without 740 // smoothing" or Mono) because those rendering modes are not 741 // gamma-aware in CoreGraphics. 742 self.gamma_lut.coregraphics_convert_to_linear( 743 &mut rasterized_pixels, 744 ); 745 } 746 747 for pixel in rasterized_pixels.chunks_mut(4) { 748 pixel[0] = 255 - pixel[0]; 749 pixel[1] = 255 - pixel[1]; 750 pixel[2] = 255 - pixel[2]; 751 752 // Set alpha to the value of the green channel. For grayscale 753 // text, all three channels have the same value anyway. 754 // For subpixel text, the mask's alpha only makes a difference 755 // when computing the destination alpha on destination pixels 756 // that are not completely opaque. Picking an alpha value 757 // that's somehow based on the mask at least ensures that text 758 // blending doesn't modify the destination alpha on pixels where 759 // the mask is entirely zero. 760 pixel[3] = pixel[1]; 761 } 762 763 if smooth { 764 // Convert back from linear space into device space, and perform 765 // some "preblending" based on the text color. 766 // In Alpha + smoothing mode, this will also convert subpixel AA 767 // into grayscale AA. 768 self.gamma_correct_pixels( 769 &mut rasterized_pixels, 770 font.render_mode, 771 font.color, 772 ); 773 } 774 } 775 776 Ok(RasterizedGlyph { 777 left: metrics.rasterized_left as f32, 778 top: metrics.rasterized_ascent as f32, 779 width: metrics.rasterized_width, 780 height: metrics.rasterized_height, 781 scale: match glyph_type { 782 GlyphType::Bitmap => y_scale.recip() as f32, 783 GlyphType::Vector => 1.0, 784 }, 785 format: match glyph_type { 786 GlyphType::Bitmap => GlyphFormat::ColorBitmap, 787 GlyphType::Vector => font.get_glyph_format(), 788 }, 789 bytes: rasterized_pixels, 790 is_packed_glyph: false, 791 })}) 792 } 793 } 794 795 // Avoids taking locks by recycling Core Graphics contexts. 796 #[allow(dead_code)] 797 struct GraphicsContext { 798 vector_context: CGContext, 799 vector_context_size: Size2D<u32>, 800 bitmap_context: CGContext, 801 bitmap_context_size: Size2D<u32>, 802 } 803 804 impl GraphicsContext { 805 fn new() -> GraphicsContext { 806 let size = Size2D::new(INITIAL_CG_CONTEXT_SIDE_LENGTH, INITIAL_CG_CONTEXT_SIDE_LENGTH); 807 GraphicsContext { 808 vector_context: GraphicsContext::create_cg_context(&size, GlyphType::Vector), 809 vector_context_size: size, 810 bitmap_context: GraphicsContext::create_cg_context(&size, GlyphType::Bitmap), 811 bitmap_context_size: size, 812 } 813 } 814 815 #[allow(dead_code)] 816 fn get_context(&mut self, size: &Size2D<u32>, glyph_type: GlyphType) 817 -> &mut CGContext { 818 let (cached_context, cached_size) = match glyph_type { 819 GlyphType::Vector => { 820 (&mut self.vector_context, &mut self.vector_context_size) 821 } 822 GlyphType::Bitmap => { 823 (&mut self.bitmap_context, &mut self.bitmap_context_size) 824 } 825 }; 826 let rounded_size = Size2D::new(size.width.next_power_of_two(), 827 size.height.next_power_of_two()); 828 if rounded_size.width > cached_size.width || rounded_size.height > cached_size.height { 829 *cached_size = Size2D::new(u32::max(cached_size.width, rounded_size.width), 830 u32::max(cached_size.height, rounded_size.height)); 831 *cached_context = GraphicsContext::create_cg_context(cached_size, glyph_type); 832 } 833 cached_context 834 } 835 836 #[allow(dead_code)] 837 fn get_rasterized_pixels(&mut self, size: &Size2D<u32>, glyph_type: GlyphType) 838 -> Vec<u8> { 839 let (cached_context, cached_size) = match glyph_type { 840 GlyphType::Vector => (&mut self.vector_context, &self.vector_context_size), 841 GlyphType::Bitmap => (&mut self.bitmap_context, &self.bitmap_context_size), 842 }; 843 let cached_data = cached_context.data(); 844 let cached_stride = cached_size.width as usize * 4; 845 846 let result_len = size.width as usize * size.height as usize * 4; 847 let mut result = Vec::with_capacity(result_len); 848 for y in (cached_size.height - size.height)..cached_size.height { 849 let cached_start = y as usize * cached_stride; 850 let cached_end = cached_start + size.width as usize * 4; 851 result.extend_from_slice(&cached_data[cached_start..cached_end]); 852 } 853 debug_assert_eq!(result.len(), result_len); 854 result 855 } 856 857 fn create_cg_context(size: &Size2D<u32>, glyph_type: GlyphType) -> CGContext { 858 // The result of rasterization, in all render modes, is going to be a 859 // BGRA surface with white text on transparency using premultiplied 860 // alpha. For subpixel text, the RGB values will be the mask value for 861 // the individual components. For bitmap glyphs, the RGB values will be 862 // the (premultiplied) color of the pixel. For Alpha and Mono, each 863 // pixel will have R==G==B==A at the end of this function. 864 // We access the color channels in little-endian order. 865 // The CGContext will create and own our pixel buffer. 866 // In the non-Bitmap cases, we will ask CoreGraphics to draw text onto 867 // an opaque background. In order to hit the most efficient path in CG 868 // for this, we will tell CG that the CGContext is opaque, by passing 869 // an "[...]AlphaNone[...]" context flag. This creates a slight 870 // contradiction to the way we use the buffer after CG is done with it, 871 // because we will convert it into text-on-transparency. But that's ok; 872 // we still get four bytes per pixel and CG won't mess with the alpha 873 // channel after we've stopped calling CG functions. We just need to 874 // make sure that we don't look at the alpha values of the pixels that 875 // we get from CG, and compute our own alpha value only from RGB. 876 // Note that CG requires kCGBitmapByteOrder32Little in order to do 877 // subpixel AA at all (which we need it to do in both Subpixel and 878 // Alpha+smoothing mode). But little-endian is what we want anyway, so 879 // this works out nicely. 880 let color_type = match glyph_type { 881 GlyphType::Vector => kCGImageAlphaNoneSkipFirst, 882 GlyphType::Bitmap => kCGImageAlphaPremultipliedFirst, 883 }; 884 885 CGContext::create_bitmap_context(None, 886 size.width as usize, 887 size.height as usize, 888 8, 889 size.width as usize * 4, 890 &CGColorSpace::create_device_rgb(), 891 kCGBitmapByteOrder32Little | color_type) 892 } 893 } 894 895 #[derive(Clone, Copy, PartialEq, Debug)] 896 enum GlyphType { 897 Vector, 898 Bitmap, 899 }