1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2024-11-18 13:55:23 -05:00

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.
This commit is contained in:
Ben Pye 2019-04-23 10:41:21 -07:00 committed by Christian Duerr
parent cf1a35bcb4
commit b0efa9d105
6 changed files with 232 additions and 204 deletions

View file

@ -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

53
Cargo.lock generated
View file

@ -443,17 +443,6 @@ name = "core-foundation-sys"
version = "0.6.2"
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"
@ -465,17 +454,6 @@ dependencies = [
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[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"
@ -634,6 +612,18 @@ name = "dunce"
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"
@ -788,28 +778,15 @@ 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"
@ -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"

View file

@ -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" }

209
font/src/directwrite/mod.rs Normal file
View file

@ -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"),
}
}
}

View file

@ -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")]

View file

@ -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."),
}
}
}