alacritty

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit b0efa9d105b53211d8df094238c7eb8324e93566
parent cf1a35bcb471b293ced201284a888f40a555f274
Author: Ben Pye <ben@curlybracket.co.uk>
Date:   Tue, 23 Apr 2019 10:41:21 -0700

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:
MCHANGELOG.md | 6++++++
MCargo.lock | 53++++++++++++++---------------------------------------
Mfont/Cargo.toml | 3+--
Afont/src/directwrite/mod.rs | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mfont/src/lib.rs | 4++--
Dfont/src/rusttype/mod.rs | 161-------------------------------------------------------------------------------
6 files changed, 232 insertions(+), 204 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- On Windows, Alacritty will now use the native DirectWrite font API + ## Version 0.3.2 ### Fixed diff --git a/Cargo.lock b/Cargo.lock @@ -445,17 +445,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "core-graphics" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "core-graphics" version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -467,17 +456,6 @@ dependencies = [ [[package]] name = "core-text" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "core-graphics 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", - "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "core-text" version = "13.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ @@ -635,6 +613,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "dwrote" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "either" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -788,29 +778,16 @@ dependencies = [ "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)", "core-text 13.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dwrote 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.19.8 (registry+https://github.com/rust-lang/crates.io-index)", - "font-loader 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "freetype-rs 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rusttype 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "servo-fontconfig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "font-loader" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "core-text 10.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)", - "servo-fontconfig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2898,9 +2875,7 @@ dependencies = [ "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" "checksum core-foundation-sys 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "716c271e8613ace48344f723b60b900a93150271e5be206212d052bbc0883efa" "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" -"checksum core-graphics 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e54c4ab33705fa1fc8af375bb7929d68e1c1546c1ecef408966d8c3e49a1d84a" "checksum core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" -"checksum core-text 10.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "81f59bff773954e5cd058a3f5983406b52bec7cc65202bef340ba64a0c40ac91" "checksum core-text 13.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d95a72b5e50e549969dd88eff3047495fe5b8c6f028635442c2b708be707e669" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" @@ -2919,6 +2894,7 @@ dependencies = [ "checksum downcast-rs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b92dfd5c2f75260cbf750572f95d387e7ca0ba5e3fbe9e1a33f23025be020f" "checksum dtoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d301140eb411af13d3115f9a562c85cc6b541ade9dfa314132244aaee7489dd" "checksum dunce 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0ad6bf6a88548d1126045c413548df1453d9be094a8ab9fd59bf1fdd338da4f" +"checksum dwrote 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0bd1369e02db5e9b842a9b67bce8a2fcc043beafb2ae8a799dd482d46ea1ff0d" "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum embed-resource 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee11dd277e159f3a7845341f8c800899cf918a366956e31dd52f35eff638a403" "checksum encoding_rs 0.8.17 (registry+https://github.com/rust-lang/crates.io-index)" = "4155785c79f2f6701f185eb2e6b4caf0555ec03477cb4c70db67b465311620ed" @@ -2935,7 +2911,6 @@ dependencies = [ "checksum filetime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a2df5c1a8c4be27e7707789dc42ae65976e60b394afd293d1419ab915833e646" "checksum flate2 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f87e68aa82b2de08a6e037f1385455759df6e445a8df5e005b4297191dbf18aa" "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -"checksum font-loader 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ece0e8a5dd99a65f8de977b4a3f89e3b5a5259e15ae610952cdb894e96f5e2e" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" "checksum freetype-rs 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28cc92a7040ee7b631e4279e263f9a83aedc1eb6085c68d8ca4d072b5644e705" diff --git a/font/Cargo.toml b/font/Cargo.toml @@ -22,5 +22,4 @@ core-graphics = "0.17" core-foundation-sys = "0.6" [target.'cfg(windows)'.dependencies] -font-loader = "0.8.0" -rusttype = "0.7.5" +dwrote = { version = "0.9.0" } diff --git a/font/src/directwrite/mod.rs 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"), + } + } +} diff --git a/font/src/lib.rs b/font/src/lib.rs @@ -56,9 +56,9 @@ pub mod ft; pub use ft::{Error, FreeTypeRasterizer as Rasterizer}; #[cfg(windows)] -pub mod rusttype; +pub mod directwrite; #[cfg(windows)] -pub use crate::rusttype::{Error, RustTypeRasterizer as Rasterizer}; +pub use crate::directwrite::{DirectWriteRasterizer as Rasterizer, Error}; // If target is macos, reexport everything from darwin #[cfg(target_os = "macos")] diff --git a/font/src/rusttype/mod.rs b/font/src/rusttype/mod.rs @@ -1,161 +0,0 @@ -extern crate font_loader; -use self::font_loader::system_fonts; - -extern crate rusttype; -use self::rusttype::{point, Codepoint, FontCollection, Scale}; - -use super::{FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight}; - -pub struct RustTypeRasterizer { - fonts: Vec<rusttype::Font<'static>>, - dpi_ratio: f32, -} - -impl crate::Rasterize for RustTypeRasterizer { - type Err = Error; - - fn new(device_pixel_ratio: f32, _: bool) -> Result<RustTypeRasterizer, Error> { - Ok(RustTypeRasterizer { fonts: Vec::new(), dpi_ratio: device_pixel_ratio }) - } - - fn metrics(&self, key: FontKey, size: Size) -> Result<Metrics, Error> { - let scale = Scale::uniform(size.as_f32_pts() * self.dpi_ratio * 96. / 72.); - let vmetrics = self.fonts[key.token as usize].v_metrics(scale); - let hmetrics = self.fonts[key.token as usize] - .glyph( - // If the font is monospaced all glyphs *should* have the same width - // 33 '!' is the first displaying character - Codepoint(33), - ) - .scaled(scale) - .h_metrics(); - - let line_height = f64::from(vmetrics.ascent - vmetrics.descent + vmetrics.line_gap); - let average_advance = f64::from(hmetrics.advance_width); - let descent = vmetrics.descent; - - // Strikeout and underline metrics. - // RustType doesn't support these, so we make up our own. - let thickness = (descent / 5.).round(); - let underline_position = descent / 2.; - let strikeout_position = line_height as f32 / 2. - descent; - - Ok(Metrics { - descent, - average_advance, - line_height, - underline_position, - underline_thickness: thickness, - strikeout_position, - strikeout_thickness: thickness, - }) - } - - fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Result<FontKey, Error> { - let fp = system_fonts::FontPropertyBuilder::new().family(&desc.name).monospace(); - - let fp = match desc.style { - Style::Specific(ref style) => match style.to_lowercase().as_str() { - "italic" => fp.italic(), - "bold" => fp.bold(), - _ => fp, - }, - Style::Description { slant, weight } => { - let fp = match slant { - Slant::Normal => fp, - Slant::Italic => fp.italic(), - // This style is not supported by rust-font-loader - Slant::Oblique => return Err(Error::UnsupportedStyle), - }; - match weight { - Weight::Bold => fp.bold(), - Weight::Normal => fp, - } - }, - }; - self.fonts.push( - FontCollection::from_bytes( - system_fonts::get(&fp.build()).ok_or_else(|| Error::MissingFont(desc.clone()))?.0, - ) - .and_then(FontCollection::into_font) - .map_err(|_| Error::UnsupportedFont)?, - ); - Ok(FontKey { token: (self.fonts.len() - 1) as u16 }) - } - - fn get_glyph(&mut self, glyph_key: GlyphKey) -> Result<RasterizedGlyph, Error> { - let scaled_glyph = self.fonts[glyph_key.font_key.token as usize] - .glyph(glyph_key.c) - .scaled(Scale::uniform(glyph_key.size.as_f32_pts() * self.dpi_ratio * 96. / 72.)); - - let glyph = scaled_glyph.positioned(point(0.0, 0.0)); - - // Pixel bounding box - let bb = match glyph.pixel_bounding_box() { - Some(bb) => bb, - // Bounding box calculation fails for spaces so we provide a placeholder bounding box - None => rusttype::Rect { min: point(0, 0), max: point(0, 0) }, - }; - - let mut buf = Vec::with_capacity((bb.width() * bb.height()) as usize); - - glyph.draw(|_x, _y, v| { - buf.push((v * 255.0) as u8); - buf.push((v * 255.0) as u8); - buf.push((v * 255.0) as u8); - }); - Ok(RasterizedGlyph { - c: glyph_key.c, - width: bb.width(), - height: bb.height(), - top: -bb.min.y, - left: bb.min.x, - buf, - }) - } - - fn update_dpr(&mut self, device_pixel_ratio: f32) { - self.dpi_ratio = device_pixel_ratio; - } -} - -#[derive(Debug)] -pub enum Error { - MissingFont(FontDesc), - UnsupportedFont, - UnsupportedStyle, - // NOTE: This error is different from how the FreeType code handles it - MissingGlyph, -} - -impl ::std::error::Error for Error { - fn description(&self) -> &str { - match *self { - Error::MissingFont(ref _desc) => "Couldn't find the requested font", - Error::UnsupportedFont => "Only TrueType fonts are supported", - Error::UnsupportedStyle => "The selected style is not supported by rusttype", - Error::MissingGlyph => "The selected font does not have the requested glyph", - } - } -} - -impl ::std::fmt::Display for Error { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match *self { - Error::MissingFont(ref desc) => write!( - f, - "Couldn't find a font with {}\n\tPlease check the font config in your \ - alacritty.yml.", - desc - ), - Error::UnsupportedFont => write!( - f, - "Rusttype only supports TrueType fonts.\n\tPlease select a TrueType font instead." - ), - Error::UnsupportedStyle => { - write!(f, "The selected font style is not supported by rusttype.") - }, - Error::MissingGlyph => write!(f, "The selected font did not have the requested glyph."), - } - } -}