diff options
author | Ben Pye <ben@curlybracket.co.uk> | 2019-04-23 10:41:21 -0700 |
---|---|---|
committer | Christian Duerr <chrisduerr@users.noreply.github.com> | 2019-04-23 17:41:21 +0000 |
commit | b0efa9d105b53211d8df094238c7eb8324e93566 (patch) | |
tree | b8ff924a328f2fdc807d319ad9967a6015509d90 /font/src/directwrite | |
parent | cf1a35bcb471b293ced201284a888f40a555f274 (diff) | |
download | alacritty-b0efa9d105b53211d8df094238c7eb8324e93566.tar.gz alacritty-b0efa9d105b53211d8df094238c7eb8324e93566.zip |
Add DirectWrite font rasterizer
This adds a DirectWrite font rasterizer for Windows and enables
subpixel rendering and hinting.
It also completely replaces rusttype for font rendering on Windows,
allowing Alacritty to use the native font stacks on all operating systems.
Fixes #1673.
Fixes #2316.
Diffstat (limited to 'font/src/directwrite')
-rw-r--r-- | font/src/directwrite/mod.rs | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/font/src/directwrite/mod.rs b/font/src/directwrite/mod.rs new file mode 100644 index 00000000..0284b397 --- /dev/null +++ b/font/src/directwrite/mod.rs @@ -0,0 +1,209 @@ +// 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::{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 glyph_analysis = GlyphRunAnalysis::create( + &glyph_run, + self.device_pixel_ratio * (96.0 / 72.0), + None, + dwrote::DWRITE_RENDERING_MODE_NATURAL, + 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, + }) + } + + 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"), + } + } +} |