Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{FontInstanceFlags, FontKey, FontRenderMode, FontVariation};
use api::{ColorU, GlyphDimensions, NativeFontHandle};
use crate::gamma_lut::{ColorLut, GammaLut};
use crate::rasterizer::{FontInstance, FontTransform, GlyphKey};
use crate::rasterizer::{GlyphFormat, GlyphRasterError, GlyphRasterResult, RasterizedGlyph};
use crate::rasterizer::apply_multistrike_bold;
use crate::types::{FastHashMap, FastHashSet};
use std::borrow::Borrow;
use std::collections::hash_map::Entry;
use std::hash::{Hash, Hasher};
use std::path::Path;
use std::sync::{Arc, Mutex};
use api::FontInstancePlatformOptions;
use std::mem;
lazy_static! {
static ref DEFAULT_FONT_DESCRIPTOR: dwrote::FontDescriptor = dwrote::FontDescriptor {
family_name: "Arial".to_owned(),
weight: dwrote::FontWeight::Regular,
stretch: dwrote::FontStretch::Normal,
style: dwrote::FontStyle::Normal,
};
}
type CachedFontKey = Arc<Path>;
// A cached dwrote font file that is shared among all faces.
// Each face holds a CachedFontKey to keep track of how many users of the font there are.
struct CachedFont {
key: CachedFontKey,
file: dwrote::FontFile,
}
// FontFile contains a ComPtr<IDWriteFontFile>, but DWrite font files are threadsafe.
unsafe impl Send for CachedFont {}
impl PartialEq for CachedFont {
fn eq(&self, other: &CachedFont) -> bool {
self.key == other.key
}
}
impl Eq for CachedFont {}
impl Hash for CachedFont {
fn hash<H: Hasher>(&self, state: &mut H) {
self.key.hash(state);
}
}
impl Borrow<Path> for CachedFont {
fn borrow(&self) -> &Path {
&*self.key
}
}
lazy_static! {
// This is effectively a weak map of dwrote FontFiles. CachedFonts are entered into the
// cache when there are any FontFaces using them. CachedFonts are removed from the cache
// when there are no more FontFaces using them at all.
static ref FONT_CACHE: Mutex<FastHashSet<CachedFont>> = Mutex::new(FastHashSet::default());
}
struct FontFace {
cached: Option<CachedFontKey>,
file: dwrote::FontFile,
index: u32,
face: dwrote::FontFace,
}
pub struct FontContext {
fonts: FastHashMap<FontKey, FontFace>,
variations: FastHashMap<(FontKey, dwrote::DWRITE_FONT_SIMULATIONS, Vec<FontVariation>), dwrote::FontFace>,
gamma_luts: FastHashMap<(u16, u8), GammaLut>,
}
// DirectWrite is safe to use on multiple threads and non-shareable resources are
// all hidden inside their font context.
unsafe impl Send for FontContext {}
fn dwrite_texture_type(render_mode: FontRenderMode) -> dwrote::DWRITE_TEXTURE_TYPE {
match render_mode {
FontRenderMode::Mono => dwrote::DWRITE_TEXTURE_ALIASED_1x1,
FontRenderMode::Alpha |
FontRenderMode::Subpixel => dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1,
}
}
fn dwrite_measure_mode(
font: &FontInstance,
bitmaps: bool,
) -> dwrote::DWRITE_MEASURING_MODE {
if bitmaps || font.flags.contains(FontInstanceFlags::FORCE_GDI) {
dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC
} else {
match font.render_mode {
FontRenderMode::Mono => dwrote::DWRITE_MEASURING_MODE_GDI_CLASSIC,
FontRenderMode::Alpha | FontRenderMode::Subpixel => dwrote::DWRITE_MEASURING_MODE_NATURAL,
}
}
}
fn dwrite_render_mode(
font_face: &dwrote::FontFace,
font: &FontInstance,
em_size: f32,
measure_mode: dwrote::DWRITE_MEASURING_MODE,
bitmaps: bool,
) -> dwrote::DWRITE_RENDERING_MODE {
let dwrite_render_mode = match font.render_mode {
FontRenderMode::Mono => dwrote::DWRITE_RENDERING_MODE_ALIASED,
FontRenderMode::Alpha | FontRenderMode::Subpixel => {
if bitmaps || font.flags.contains(FontInstanceFlags::FORCE_GDI) {
dwrote::DWRITE_RENDERING_MODE_GDI_CLASSIC
} else if font.flags.contains(FontInstanceFlags::FORCE_SYMMETRIC) {
dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC
} else if font.flags.contains(FontInstanceFlags::NO_SYMMETRIC) {
dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL
} else {
font_face.get_recommended_rendering_mode_default_params(em_size, 1.0, measure_mode)
}
}
};
if dwrite_render_mode == dwrote::DWRITE_RENDERING_MODE_OUTLINE {
// Outline mode is not supported
return dwrote::DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL_SYMMETRIC;
}
dwrite_render_mode
}
fn is_bitmap_font(font: &FontInstance) -> bool {
// If bitmaps are requested, then treat as a bitmap font to disable transforms.
// If mono AA is requested, let that take priority over using bitmaps.
font.render_mode != FontRenderMode::Mono &&
font.flags.contains(FontInstanceFlags::EMBEDDED_BITMAPS)
}
impl FontContext {
pub fn distribute_across_threads() -> bool {
true
}
pub fn new() -> FontContext {
FontContext {
fonts: FastHashMap::default(),
variations: FastHashMap::default(),
gamma_luts: FastHashMap::default(),
}
}
fn add_font_descriptor(&mut self, font_key: &FontKey, desc: &dwrote::FontDescriptor) {
let system_fc = dwrote::FontCollection::get_system(false);
if let Some(font) = system_fc.get_font_from_descriptor(desc) {
let face = font.create_font_face();
let file = face.get_files().pop().unwrap();
let index = face.get_index();
self.fonts.insert(*font_key, FontFace { cached: None, file, index, face });
}
}
pub fn add_raw_font(&mut self, font_key: &FontKey, data: Arc<Vec<u8>>, index: u32) {
if self.fonts.contains_key(font_key) {
return;
}
if let Some(file) = dwrote::FontFile::new_from_data(data) {
if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) {
self.fonts.insert(*font_key, FontFace { cached: None, file, index, face });
return;
}
}
// XXX add_raw_font needs to have a way to return an error
debug!("DWrite WR failed to load font from data, using Arial instead");
self.add_font_descriptor(font_key, &DEFAULT_FONT_DESCRIPTOR);
}
pub fn add_native_font(&mut self, font_key: &FontKey, font_handle: NativeFontHandle) {
if self.fonts.contains_key(font_key) {
return;
}
let index = font_handle.index;
let mut cache = FONT_CACHE.lock().unwrap();
// Check to see if the font is already in the cache. If so, reuse it.
if let Some(font) = cache.get(font_handle.path.as_path()) {
if let Ok(face) = font.file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) {
self.fonts.insert(
*font_key,
FontFace { cached: Some(font.key.clone()), file: font.file.clone(), index, face },
);
return;
}
}
if let Some(file) = dwrote::FontFile::new_from_path(&font_handle.path) {
// The font is not in the cache yet, so try to create the font and insert it in the cache.
if let Ok(face) = file.create_face(index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) {
let key: CachedFontKey = font_handle.path.into();
self.fonts.insert(
*font_key,
FontFace { cached: Some(key.clone()), file: file.clone(), index, face },
);
cache.insert(CachedFont { key, file });
return;
}
}
// XXX add_native_font needs to have a way to return an error
debug!("DWrite WR failed to load font from path, using Arial instead");
self.add_font_descriptor(font_key, &DEFAULT_FONT_DESCRIPTOR);
}
pub fn delete_font(&mut self, font_key: &FontKey) {
if let Some(face) = self.fonts.remove(font_key) {
self.variations.retain(|k, _| k.0 != *font_key);
// Check if this was a cached font.
if let Some(key) = face.cached {
let mut cache = FONT_CACHE.lock().unwrap();
// If there are only two references left, that means only this face and
// the cache are using the font. So remove it from the cache.
if Arc::strong_count(&key) == 2 {
cache.remove(&*key);
}
}
}
}
pub fn delete_font_instance(&mut self, instance: &FontInstance) {
// Ensure we don't keep around excessive amounts of stale variations.
if !instance.variations.is_empty() {
let sims = if instance.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
dwrote::DWRITE_FONT_SIMULATIONS_BOLD
} else {
dwrote::DWRITE_FONT_SIMULATIONS_NONE
};
self.variations.remove(&(instance.font_key, sims, instance.variations.clone()));
}
}
// Assumes RGB format from dwrite, which is 3 bytes per pixel as dwrite
// doesn't output an alpha value via GlyphRunAnalysis::CreateAlphaTexture
#[allow(dead_code)]
fn print_glyph_data(&self, data: &[u8], width: usize, height: usize) {
// Rust doesn't have step_by support on stable :(
for i in 0 .. height {
let current_height = i * width * 3;
for pixel in data[current_height .. current_height + (width * 3)].chunks(3) {
let r = pixel[0];
let g = pixel[1];
let b = pixel[2];
debug!("({}, {}, {}) ", r, g, b,);
}
}
}
fn get_font_face(
&mut self,
font: &FontInstance,
) -> &dwrote::FontFace {
if !font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) &&
font.variations.is_empty() {
return &self.fonts.get(&font.font_key).unwrap().face;
}
let sims = if font.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
dwrote::DWRITE_FONT_SIMULATIONS_BOLD
} else {
dwrote::DWRITE_FONT_SIMULATIONS_NONE
};
match self.variations.entry((font.font_key, sims, font.variations.clone())) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let normal_face = self.fonts.get(&font.font_key).unwrap();
if !font.variations.is_empty() {
if let Some(var_face) = normal_face.face.create_font_face_with_variations(
sims,
&font.variations.iter().map(|var| {
dwrote::DWRITE_FONT_AXIS_VALUE {
// OpenType tags are big-endian, but DWrite wants little-endian.
axisTag: var.tag.swap_bytes(),
value: var.value,
}
}).collect::<Vec<_>>(),
) {
return entry.insert(var_face);
}
}
let var_face = normal_face.file
.create_face(normal_face.index, sims)
.unwrap_or_else(|_| normal_face.face.clone());
entry.insert(var_face)
}
}
}
fn create_glyph_analysis(
&mut self,
font: &FontInstance,
key: &GlyphKey,
size: f32,
transform: Option<dwrote::DWRITE_MATRIX>,
bitmaps: bool,
) -> Result<(dwrote::GlyphRunAnalysis, dwrote::DWRITE_TEXTURE_TYPE, dwrote::RECT), dwrote::HRESULT> {
let face = self.get_font_face(font);
let glyph = key.index() as u16;
let advance = 0.0f32;
let offset = dwrote::GlyphOffset {
advanceOffset: 0.0,
ascenderOffset: 0.0,
};
let glyph_run = dwrote::DWRITE_GLYPH_RUN {
fontFace: unsafe { face.as_ptr() },
fontEmSize: size, // size in DIPs (1/96", same as CSS pixels)
glyphCount: 1,
glyphIndices: &glyph,
glyphAdvances: &advance,
glyphOffsets: &offset,
isSideways: 0,
bidiLevel: 0,
};
let dwrite_measure_mode = dwrite_measure_mode(font, bitmaps);
let dwrite_render_mode = dwrite_render_mode(
face,
font,
size,
dwrite_measure_mode,
bitmaps,
);
let analysis = dwrote::GlyphRunAnalysis::create(
&glyph_run,
1.0,
transform,
dwrite_render_mode,
dwrite_measure_mode,
0.0,
0.0,
)?;
let texture_type = dwrite_texture_type(font.render_mode);
let bounds = analysis.get_alpha_texture_bounds(texture_type)?;
// If the bounds are empty, then we might not be able to render the glyph with cleartype.
// Try again with aliased rendering to check if that works instead.
if font.render_mode != FontRenderMode::Mono &&
(bounds.left == bounds.right || bounds.top == bounds.bottom) {
let analysis2 = dwrote::GlyphRunAnalysis::create(
&glyph_run,
1.0,
transform,
dwrote::DWRITE_RENDERING_MODE_ALIASED,
dwrite_measure_mode,
0.0,
0.0,
)?;
let bounds2 = analysis2.get_alpha_texture_bounds(dwrote::DWRITE_TEXTURE_ALIASED_1x1)?;
if bounds2.left != bounds2.right && bounds2.top != bounds2.bottom {
return Ok((analysis2, dwrote::DWRITE_TEXTURE_ALIASED_1x1, bounds2));
}
}
Ok((analysis, texture_type, bounds))
}
pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
let face = &self.fonts.get(&font_key).unwrap().face;
let indices = face.get_glyph_indices(&[ch as u32]);
indices.first().map(|idx| *idx as u32)
}
pub fn get_glyph_dimensions(
&mut self,
font: &FontInstance,
key: &GlyphKey,
) -> Option<GlyphDimensions> {
let (size, x_scale, y_scale, bitmaps, transform) = Self::get_glyph_parameters(font, key);
let (_, _, bounds) = self.create_glyph_analysis(font, key, size, transform, bitmaps).ok()?;
let width = (bounds.right - bounds.left) as i32;
let height = (bounds.bottom - bounds.top) as i32;
// Alpha texture bounds can sometimes return an empty rect
// Such as for spaces
if width == 0 || height == 0 {
return None;
}
let (strike_scale, pixel_step) = if bitmaps {
(y_scale, 1.0)
} else {
(x_scale, y_scale / x_scale)
};
let extra_strikes = font.get_extra_strikes(FontInstanceFlags::MULTISTRIKE_BOLD, strike_scale);
let extra_width = extra_strikes as f64 * pixel_step;
let face = self.get_font_face(font);
face.get_design_glyph_metrics(&[key.index() as u16], false)
.first()
.map(|metrics| {
let em_size = size / 16.;
let design_units_per_pixel = face.metrics().metrics0().designUnitsPerEm as f32 / 16. as f32;
let scaled_design_units_to_pixels = em_size / design_units_per_pixel;
let advance = metrics.advanceWidth as f32 * scaled_design_units_to_pixels;
GlyphDimensions {
left: bounds.left,
top: -bounds.top,
width: width + extra_width.ceil() as i32,
height,
advance: advance + extra_width as f32,
}
})
}
// DWrite ClearType gives us values in RGB, but WR expects BGRA.
fn convert_to_bgra(
&self,
pixels: &[u8],
width: usize,
height: usize,
texture_type: dwrote::DWRITE_TEXTURE_TYPE,
render_mode: FontRenderMode,
bitmaps: bool,
subpixel_bgr: bool,
padding: usize,
) -> (Vec<u8>, bool) {
let (buffer_width, buffer_height) = (width + padding * 2, height + padding * 2);
let buffer_length = buffer_width * buffer_height * 4;
let mut bgra_pixels: Vec<u8> = vec![0; buffer_length];
match (texture_type, render_mode, bitmaps) {
(dwrote::DWRITE_TEXTURE_ALIASED_1x1, _, _) => {
assert!(width * height == pixels.len());
let mut i = 0;
for row in padding .. height + padding {
let row_offset = row * buffer_width;
for col in padding .. width + padding {
let offset = (row_offset + col) * 4;
let alpha = pixels[i];
i += 1;
bgra_pixels[offset + 0] = alpha;
bgra_pixels[offset + 1] = alpha;
bgra_pixels[offset + 2] = alpha;
bgra_pixels[offset + 3] = alpha;
}
}
(bgra_pixels, false)
}
(_, FontRenderMode::Subpixel, false) => {
assert!(width * height * 3 == pixels.len());
let mut i = 0;
for row in padding .. height + padding {
let row_offset = row * buffer_width;
for col in padding .. width + padding {
let offset = (row_offset + col) * 4;
let (mut r, g, mut b) = (pixels[i + 0], pixels[i + 1], pixels[i + 2]);
if subpixel_bgr {
mem::swap(&mut r, &mut b);
}
i += 3;
bgra_pixels[offset + 0] = b;
bgra_pixels[offset + 1] = g;
bgra_pixels[offset + 2] = r;
bgra_pixels[offset + 3] = 0xff;
}
}
(bgra_pixels, true)
}
_ => {
assert!(width * height * 3 == pixels.len());
let mut i = 0;
for row in padding .. height + padding {
let row_offset = row * buffer_width;
for col in padding .. width + padding {
let offset = (row_offset + col) * 4;
// Only take the G channel, as its closest to D2D
let alpha = pixels[i + 1] as u8;
i += 3;
bgra_pixels[offset + 0] = alpha;
bgra_pixels[offset + 1] = alpha;
bgra_pixels[offset + 2] = alpha;
bgra_pixels[offset + 3] = alpha;
}
}
(bgra_pixels, false)
}
}
}
pub fn prepare_font(font: &mut FontInstance) {
match font.render_mode {
FontRenderMode::Mono => {
// In mono mode the color of the font is irrelevant.
font.color = ColorU::new(255, 255, 255, 255);
// Subpixel positioning is disabled in mono mode.
font.disable_subpixel_position();
}
FontRenderMode::Alpha => {
font.color = font.color.luminance_color().quantize();
}
FontRenderMode::Subpixel => {
font.color = font.color.quantize();
}
}
}
fn get_glyph_parameters(font: &FontInstance, key: &GlyphKey)
-> (f32, f64, f64, bool, Option<dwrote::DWRITE_MATRIX>) {
let (x_scale, y_scale) = font.transform.compute_scale().unwrap_or((1.0, 1.0));
let scaled_size = font.size.to_f64_px() * y_scale;
let bitmaps = is_bitmap_font(font);
let (mut shape, (mut x_offset, mut y_offset)) = if bitmaps {
(FontTransform::identity(), (0.0, 0.0))
} else {
(font.transform.invert_scale(y_scale, y_scale), font.get_subpx_offset(key))
};
if font.flags.contains(FontInstanceFlags::FLIP_X) {
shape = shape.flip_x();
}
if font.flags.contains(FontInstanceFlags::FLIP_Y) {
shape = shape.flip_y();
}
if font.flags.contains(FontInstanceFlags::TRANSPOSE) {
shape = shape.swap_xy();
}
let (mut tx, mut ty) = (0.0, 0.0);
if font.synthetic_italics.is_enabled() {
let (shape_, (tx_, ty_)) = font.synthesize_italics(shape, scaled_size);
shape = shape_;
tx = tx_;
ty = ty_;
};
x_offset += tx;
y_offset += ty;
let transform = if !shape.is_identity() || (x_offset, y_offset) != (0.0, 0.0) {
Some(dwrote::DWRITE_MATRIX {
m11: shape.scale_x,
m12: shape.skew_y,
m21: shape.skew_x,
m22: shape.scale_y,
dx: x_offset as f32,
dy: y_offset as f32,
})
} else {
None
};
(scaled_size as f32, x_scale, y_scale, bitmaps, transform)
}
pub fn begin_rasterize(_font: &FontInstance) {
}
pub fn end_rasterize(_font: &FontInstance) {
}
pub fn rasterize_glyph(&mut self, font: &FontInstance, key: &GlyphKey) -> GlyphRasterResult {
let (size, x_scale, y_scale, bitmaps, transform) = Self::get_glyph_parameters(font, key);
let (analysis, texture_type, bounds) = self.create_glyph_analysis(font, key, size, transform, bitmaps)
.or(Err(GlyphRasterError::LoadFailed))?;
let mut width = (bounds.right - bounds.left) as i32;
let height = (bounds.bottom - bounds.top) as i32;
// Alpha texture bounds can sometimes return an empty rect
// Such as for spaces
if width == 0 || height == 0 {
return Err(GlyphRasterError::LoadFailed);
}
let pixels = analysis.create_alpha_texture(texture_type, bounds).or(Err(GlyphRasterError::LoadFailed))?;
let padding = if font.use_texture_padding() { 1 } else { 0 };
let (mut bgra_pixels, is_subpixel) = self.convert_to_bgra(
&pixels,
width as usize,
height as usize,
texture_type,
font.render_mode,
bitmaps,
font.flags.contains(FontInstanceFlags::SUBPIXEL_BGR),
padding as usize,
);
// Apply multistrike bold, if necessary, and replace the current pixels with it.
let (strike_scale, pixel_step) = if bitmaps {
(y_scale, 1.0)
} else {
(x_scale, y_scale / x_scale)
};
let extra_strikes = font.get_extra_strikes(FontInstanceFlags::MULTISTRIKE_BOLD, strike_scale);
if extra_strikes > 0 {
let (bold_pixels, bold_width) = apply_multistrike_bold(
&bgra_pixels,
(width + padding * 2) as usize,
(height + padding * 2) as usize,
is_subpixel,
extra_strikes,
pixel_step,
);
width = bold_width as i32 - padding * 2;
bgra_pixels = bold_pixels;
}
let FontInstancePlatformOptions { gamma, contrast, cleartype_level, .. } =
font.platform_options.unwrap_or_default();
let gamma_lut = self.gamma_luts
.entry((gamma, contrast))
.or_insert_with(||
GammaLut::new(
contrast as f32 / 100.0,
gamma as f32 / 100.0,
gamma as f32 / 100.0,
));
if is_subpixel {
gamma_lut.preblend_scaled(&mut bgra_pixels, font.color, cleartype_level);
} else {
gamma_lut.preblend(&mut bgra_pixels, font.color);
}
let format = if bitmaps {
GlyphFormat::Bitmap
} else if texture_type == dwrote::DWRITE_TEXTURE_ALIASED_1x1 {
font.get_alpha_glyph_format()
} else {
font.get_glyph_format()
};
Ok(RasterizedGlyph {
left: (bounds.left - padding) as f32,
top: (-bounds.top + padding) as f32,
width: width + padding * 2,
height: height + padding * 2,
scale: (if bitmaps { y_scale.recip() } else { 1.0 }) as f32,
format,
bytes: bgra_pixels,
})
}
}