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