blob: 74f3d6e2de76f18d39ba74ca7934970ae4e4ba6a [file] [log] [blame]
// Copyright 2019 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//! Rasterization powered by DirectWrite
extern crate dwrote;
use self::dwrote::{
FontCollection, FontStretch, FontStyle, FontWeight, GlyphOffset, GlyphRunAnalysis,
};
use super::{
BitmapBuffer, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight,
};
pub struct DirectWriteRasterizer {
fonts: Vec<dwrote::FontFace>,
device_pixel_ratio: f32,
}
impl crate::Rasterize for DirectWriteRasterizer {
type Err = Error;
fn new(device_pixel_ratio: f32, _: bool) -> Result<DirectWriteRasterizer, Error> {
Ok(DirectWriteRasterizer { fonts: Vec::new(), device_pixel_ratio })
}
fn metrics(&self, key: FontKey, size: Size) -> Result<Metrics, Error> {
let font = self.fonts.get(key.token as usize).ok_or(Error::FontNotLoaded)?;
let vmetrics = font.metrics();
let scale = (size.as_f32_pts() * self.device_pixel_ratio * (96.0 / 72.0))
/ f32::from(vmetrics.designUnitsPerEm);
let underline_position = f32::from(vmetrics.underlinePosition) * scale;
let underline_thickness = f32::from(vmetrics.underlineThickness) * scale;
let strikeout_position = f32::from(vmetrics.strikethroughPosition) * scale;
let strikeout_thickness = f32::from(vmetrics.strikethroughThickness) * scale;
let ascent = f32::from(vmetrics.ascent) * scale;
let descent = -f32::from(vmetrics.descent) * scale;
let line_gap = f32::from(vmetrics.lineGap) * scale;
let line_height = f64::from(ascent - descent + line_gap);
// We assume that all monospace characters have the same width
// Because of this we take '!', the first drawable character, for measurements
let glyph_metrics = font.get_design_glyph_metrics(&[33], false);
let hmetrics = glyph_metrics.first().ok_or(Error::MissingGlyph('!'))?;
let average_advance = f64::from(hmetrics.advanceWidth) * f64::from(scale);
Ok(Metrics {
descent,
average_advance,
line_height,
underline_position,
underline_thickness,
strikeout_position,
strikeout_thickness,
})
}
fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Result<FontKey, Error> {
let system_fc = FontCollection::system();
let family = system_fc
.get_font_family_by_name(&desc.name)
.ok_or_else(|| Error::MissingFont(desc.clone()))?;
let font = match desc.style {
Style::Description { weight, slant } => {
let weight =
if weight == Weight::Bold { FontWeight::Bold } else { FontWeight::Regular };
let style = match slant {
Slant::Normal => FontStyle::Normal,
Slant::Oblique => FontStyle::Oblique,
Slant::Italic => FontStyle::Italic,
};
// This searches for the "best" font - should mean we don't have to worry about
// fallbacks if our exact desired weight/style isn't available
Ok(family.get_first_matching_font(weight, FontStretch::Normal, style))
},
Style::Specific(ref style) => {
let mut idx = 0;
let count = family.get_font_count();
loop {
if idx == count {
break Err(Error::MissingFont(desc.clone()));
}
let font = family.get_font(idx);
if font.face_name() == *style {
break Ok(font);
}
idx += 1;
}
},
}?;
let face = font.create_font_face();
self.fonts.push(face);
Ok(FontKey { token: (self.fonts.len() - 1) as u16 })
}
fn get_glyph(&mut self, glyph: GlyphKey) -> Result<RasterizedGlyph, Error> {
let font = self.fonts.get(glyph.font_key.token as usize).ok_or(Error::FontNotLoaded)?;
let offset = GlyphOffset { advanceOffset: 0.0, ascenderOffset: 0.0 };
let glyph_index = *font
.get_glyph_indices(&[glyph.c as u32])
.first()
.ok_or_else(|| Error::MissingGlyph(glyph.c))?;
if glyph_index == 0 {
// The DirectWrite documentation states that we should get 0 returned if the glyph
// does not exist in the font
return Err(Error::MissingGlyph(glyph.c));
}
let glyph_run = dwrote::DWRITE_GLYPH_RUN {
fontFace: unsafe { font.as_ptr() },
fontEmSize: glyph.size.as_f32_pts(),
glyphCount: 1,
glyphIndices: &(glyph_index),
glyphAdvances: &(0.0),
glyphOffsets: &(offset),
isSideways: 0,
bidiLevel: 0,
};
let rendering_mode = font.get_recommended_rendering_mode_default_params(
glyph.size.as_f32_pts(),
self.device_pixel_ratio * (96.0 / 72.0),
dwrote::DWRITE_MEASURING_MODE_NATURAL,
);
let glyph_analysis = GlyphRunAnalysis::create(
&glyph_run,
self.device_pixel_ratio * (96.0 / 72.0),
None,
rendering_mode,
dwrote::DWRITE_MEASURING_MODE_NATURAL,
0.0,
0.0,
)
.or_else(|_| Err(Error::MissingGlyph(glyph.c)))?;
let bounds = glyph_analysis
.get_alpha_texture_bounds(dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1)
.or_else(|_| Err(Error::MissingGlyph(glyph.c)))?;
let buf = glyph_analysis
.create_alpha_texture(dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1, bounds)
.or_else(|_| Err(Error::MissingGlyph(glyph.c)))?;
Ok(RasterizedGlyph {
c: glyph.c,
width: (bounds.right - bounds.left) as i32,
height: (bounds.bottom - bounds.top) as i32,
top: -bounds.top,
left: bounds.left,
buf: BitmapBuffer::RGB(buf),
})
}
fn update_dpr(&mut self, device_pixel_ratio: f32) {
self.device_pixel_ratio = device_pixel_ratio;
}
}
#[derive(Debug)]
pub enum Error {
MissingFont(FontDesc),
MissingGlyph(char),
FontNotLoaded,
}
impl ::std::error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::MissingFont(ref _desc) => "Couldn't find the requested font",
Error::MissingGlyph(ref _c) => "Couldn't find the requested glyph",
Error::FontNotLoaded => "Tried to operate on font that hasn't been loaded",
}
}
}
impl ::std::fmt::Display for Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
Error::MissingGlyph(ref c) => write!(f, "Glyph not found for char {:?}", c),
Error::MissingFont(ref desc) => write!(
f,
"Couldn't find a font with {}\n\tPlease check the font config in your \
alacritty.yml.",
desc
),
Error::FontNotLoaded => f.write_str("Tried to use a font that hasn't been loaded"),
}
}
}