diff --git a/Cargo.lock b/Cargo.lock index 3d3282b5d..9c8912189 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -697,6 +697,7 @@ dependencies = [ "bstr 0.2.15", "chrono", "dirs-next", + "enum-display-derive", "filenamegen", "hostname", "lazy_static", @@ -1070,6 +1071,17 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "enum-display-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16ef37b2a9b242295d61a154ee91ae884afff6b8b933b486b12481cc58310ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "enumflags2" version = "0.6.4" diff --git a/config/Cargo.toml b/config/Cargo.toml index 05e07bd16..5530c5bb3 100644 --- a/config/Cargo.toml +++ b/config/Cargo.toml @@ -17,6 +17,7 @@ bitflags = "1.0" bstr = "0.2" chrono = {version="0.4", features=["unstable-locales"]} dirs-next = "2.0" +enum-display-derive = "0.1" filenamegen = "0.2" hostname = "0.3" lazy_static = "1.4" diff --git a/config/src/font.rs b/config/src/font.rs index 85a20c920..ed5773e93 100644 --- a/config/src/font.rs +++ b/config/src/font.rs @@ -1,9 +1,135 @@ use crate::*; use bitflags::*; +use enum_display_derive::Display; use luahelper::impl_lua_conversion; use serde::{Deserialize, Deserializer, Serialize}; +use std::fmt::Display; use termwiz::color::RgbColor; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Display)] +pub enum FontWidth { + UltraCondensed, + ExtraCondensed, + Condensed, + SemiCondensed, + Normal, + SemiExpanded, + Expanded, + ExtraExpanded, + UltraExpanded, +} + +impl FontWidth { + pub fn from_opentype_width(w: u16) -> Self { + match w { + 1 => Self::UltraCondensed, + 2 => Self::ExtraCondensed, + 3 => Self::Condensed, + 4 => Self::SemiCondensed, + 5 => Self::Normal, + 6 => Self::SemiExpanded, + 7 => Self::Expanded, + 8 => Self::ExtraExpanded, + 9 => Self::UltraExpanded, + _ if w < 1 => Self::UltraCondensed, + _ => Self::UltraExpanded, + } + } + + pub fn to_opentype_width(self) -> u16 { + match self { + Self::UltraCondensed => 1, + Self::ExtraCondensed => 2, + Self::Condensed => 3, + Self::SemiCondensed => 4, + Self::Normal => 5, + Self::SemiExpanded => 6, + Self::Expanded => 7, + Self::ExtraExpanded => 8, + Self::UltraExpanded => 9, + } + } +} + +impl Default for FontWidth { + fn default() -> Self { + Self::Normal + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Display)] +pub enum FontWeight { + Thin, + ExtraLight, + Light, + DemiLight, + Book, + Regular, + Medium, + DemiBold, + Bold, + ExtraBold, + Black, + ExtraBlack, +} + +impl Default for FontWeight { + fn default() -> Self { + Self::Regular + } +} + +impl FontWeight { + pub fn from_opentype_weight(w: u16) -> Self { + if w >= 1000 { + Self::ExtraBlack + } else if w >= 900 { + Self::Black + } else if w >= 800 { + Self::ExtraBold + } else if w >= 700 { + Self::Bold + } else if w >= 600 { + Self::DemiBold + } else if w >= 500 { + Self::Medium + } else if w >= 400 { + Self::Regular + } else if w >= 380 { + Self::Book + } else if w >= 350 { + Self::DemiLight + } else if w >= 300 { + Self::Light + } else if w >= 200 { + Self::ExtraLight + } else { + Self::Thin + } + } + + pub fn to_opentype_weight(self) -> u16 { + match self { + Self::Thin => 100, + Self::ExtraLight => 200, + Self::Light => 300, + Self::DemiLight => 350, + Self::Book => 380, + Self::Regular => 400, + Self::Medium => 500, + Self::DemiBold => 600, + Self::Bold => 700, + Self::ExtraBold => 800, + Self::Black => 900, + Self::ExtraBlack => 1000, + } + } + + pub fn bolder(self) -> Self { + Self::from_opentype_weight(self.to_opentype_weight() + 200) + } +} + #[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash)] pub enum FreeTypeLoadTarget { /// This corresponds to the default hinting algorithm, optimized @@ -123,7 +249,9 @@ pub struct FontAttributes { pub family: String, /// Whether the font should be a bold variant #[serde(default)] - pub bold: bool, + pub weight: FontWeight, + #[serde(default)] + pub width: FontWidth, /// Whether the font should be an italic variant #[serde(default)] pub italic: bool, @@ -136,8 +264,8 @@ impl std::fmt::Display for FontAttributes { fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { write!( fmt, - "wezterm.font('{}', {{bold={}, italic={}}})", - self.family, self.bold, self.italic + "wezterm.font('{}', {{weight='{}', width='{}', italic={}}})", + self.family, self.weight, self.width, self.italic ) } } @@ -146,7 +274,8 @@ impl FontAttributes { pub fn new(family: &str) -> Self { Self { family: family.into(), - bold: false, + weight: FontWeight::default(), + width: FontWidth::default(), italic: false, is_fallback: false, is_synthetic: false, @@ -156,7 +285,8 @@ impl FontAttributes { pub fn new_fallback(family: &str) -> Self { Self { family: family.into(), - bold: false, + weight: FontWeight::default(), + width: FontWidth::default(), italic: false, is_fallback: true, is_synthetic: false, @@ -168,7 +298,8 @@ impl Default for FontAttributes { fn default() -> Self { Self { family: "JetBrains Mono".into(), - bold: false, + weight: FontWeight::default(), + width: FontWidth::default(), italic: false, is_fallback: false, is_synthetic: false, @@ -269,7 +400,7 @@ impl TextStyle { .iter() .map(|attr| { let mut attr = attr.clone(); - attr.bold = true; + attr.weight = attr.weight.bolder(); attr.is_synthetic = true; attr }) diff --git a/config/src/lua.rs b/config/src/lua.rs index 67414e25f..4069e641d 100644 --- a/config/src/lua.rs +++ b/config/src/lua.rs @@ -1,4 +1,4 @@ -use crate::{FontAttributes, TextStyle}; +use crate::{FontAttributes, FontWeight, FontWidth, TextStyle}; use anyhow::anyhow; use bstr::BString; pub use luahelper::*; @@ -334,7 +334,11 @@ fn hostname<'lua>(_: &'lua Lua, _: ()) -> mlua::Result { struct TextStyleAttributes { /// Whether the font should be a bold variant #[serde(default)] - pub bold: bool, + pub bold: Option, + #[serde(default)] + pub weight: Option, + #[serde(default)] + pub width: FontWidth, /// Whether the font should be an italic variant #[serde(default)] pub italic: bool, @@ -365,7 +369,12 @@ fn font<'lua>( text_style.font.clear(); text_style.font.push(FontAttributes { family, - bold: attrs.bold, + width: attrs.width, + weight: match attrs.bold { + Some(true) => FontWeight::Bold, + Some(false) => FontWeight::Regular, + None => attrs.weight.unwrap_or(FontWeight::Regular), + }, italic: attrs.italic, is_fallback: false, is_synthetic: false, @@ -393,7 +402,12 @@ fn font_with_fallback<'lua>( for (idx, family) in fallback.into_iter().enumerate() { text_style.font.push(FontAttributes { family, - bold: attrs.bold, + width: attrs.width, + weight: match attrs.bold { + Some(true) => FontWeight::Bold, + Some(false) => FontWeight::Regular, + None => attrs.weight.unwrap_or(FontWeight::Regular), + }, italic: attrs.italic, is_fallback: idx != 0, is_synthetic: false, diff --git a/deps/fontconfig/src/lib.rs b/deps/fontconfig/src/lib.rs index 984daa309..ebf9a3d6c 100644 --- a/deps/fontconfig/src/lib.rs +++ b/deps/fontconfig/src/lib.rs @@ -52,6 +52,16 @@ pub const FC_SLANT_ROMAN: c_int = 0; pub const FC_SLANT_ITALIC: c_int = 100; pub const FC_SLANT_OBLIQUE: c_int = 110; +pub const FC_WIDTH_ULTRACONDENSED: c_int = 50; +pub const FC_WIDTH_EXTRACONDENSED: c_int = 63; +pub const FC_WIDTH_CONDENSED: c_int = 75; +pub const FC_WIDTH_SEMICONDENSED: c_int = 87; +pub const FC_WIDTH_NORMAL: c_int = 100; +pub const FC_WIDTH_SEMIEXPANDED: c_int = 113; +pub const FC_WIDTH_EXPANDED: c_int = 125; +pub const FC_WIDTH_EXTRAEXPANDED: c_int = 150; +pub const FC_WIDTH_ULTRAEXPANDED: c_int = 200; + #[repr(C)] #[derive(Copy, Clone)] pub struct struct__FcMatrix { diff --git a/docs/changelog.md b/docs/changelog.md index 0925177ce..a6800fb35 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -15,6 +15,7 @@ As features stabilize some brief notes about them will accumulate here. * Fixed: the selection wouldn't always clear when the intersecting lines change [#644](https://github.com/wez/wezterm/issues/644) * Fixed: vertical alignment issue with Iosevka on Windows [#661](https://github.com/wez/wezterm/issues/661) * Fixed: support for "Variable" fonts such as Cascadia Code and Inconsolata on all platforms [#655](https://github.com/wez/wezterm/issues/655) +* New: [wezterm.font](config/lua/wezterm/font.md) and [wezterm.font_with_fallback](config/lua/wezterm.font_with_fallback.md) *attributes* parameter now allows matching more granular font weights and font widths. e.g.: `wezterm.font('Iosevka Term', {width="Expanded", weight="Regular"})` ### 20210405-110924-a5bb5be8 diff --git a/docs/config/lua/wezterm/font.md b/docs/config/lua/wezterm/font.md index 7761e7ced..e655df1d1 100644 --- a/docs/config/lua/wezterm/font.md +++ b/docs/config/lua/wezterm/font.md @@ -25,4 +25,21 @@ return { } ``` +*Since: nightly builds only* +It is now possible to specify both font weight and font width: + +* `width` - specifies the font width to select. The default value is `"Normal"`, and possible values are `"UltraCondensed"`, `"ExtraCondensed"`, `"Condensed"`, `"SemiCondensed"`, `"Normal"`, `"SemiExpanded"`, `"Expanded"`, `"ExtraExpanded"`, `"UltraExpanded"`. +* `weight` - specifies the weight of the font with more precision than `bold`. The default value is `"Regular"`, and possible values are `"Thin"`, `"ExtraLight"`, `"Light"`, `"DemiLight"`, `"Book"`, `"Regular"`, `"Medium"`, `"DemiBold"`, `"Bold"`, `"ExtraBold"`, `"Black"`, and `"ExtraBlack"`. +* `bold` - has been superseded by the new `weight` parameter and will be eventually removed. For compatibility purposes, specifying `bold=true` is equivalent to specifying `weight="Bold"`. + +Font weight matching will find the closest matching weight that is equal of +heavier to the specified weight. + +```lua +local wezterm = require 'wezterm'; + +return { + font = wezterm.font('Iosevka Term', {width="Expanded", weight="Regular"}), +} +``` diff --git a/docs/config/lua/wezterm/font_with_fallback.md b/docs/config/lua/wezterm/font_with_fallback.md index 26c3cc630..7e2536f52 100644 --- a/docs/config/lua/wezterm/font_with_fallback.md +++ b/docs/config/lua/wezterm/font_with_fallback.md @@ -14,6 +14,7 @@ return { } ``` -The second parameter behaves the same as that of `wezterm.font`. - +The *attributes* parameter behaves the same as that of [wezterm.font](font.md) +in that it allows you to specify font weight and style attributes that you +want to match. diff --git a/wezterm-font/src/fcwrap.rs b/wezterm-font/src/fcwrap.rs index 96c7a335f..b0583410e 100644 --- a/wezterm-font/src/fcwrap.rs +++ b/wezterm-font/src/fcwrap.rs @@ -2,6 +2,7 @@ #![allow(clippy::mutex_atomic)] use anyhow::{anyhow, ensure, Error}; +use config::{FontWeight, FontWidth}; pub use fontconfig::*; use std::ffi::{CStr, CString}; use std::fmt; @@ -446,3 +447,33 @@ impl fmt::Debug for Pattern { ) } } + +pub fn to_fc_weight(weight: FontWeight) -> c_int { + match weight { + FontWeight::Thin => FC_WEIGHT_THIN, + FontWeight::ExtraLight => FC_WEIGHT_EXTRALIGHT, + FontWeight::Light => FC_WEIGHT_LIGHT, + FontWeight::DemiLight | FontWeight::Book => FC_WEIGHT_BOOK, + FontWeight::Regular => FC_WEIGHT_REGULAR, + FontWeight::Medium => FC_WEIGHT_MEDIUM, + FontWeight::DemiBold => FC_WEIGHT_DEMIBOLD, + FontWeight::Bold => FC_WEIGHT_BOLD, + FontWeight::ExtraBold => FC_WEIGHT_EXTRABOLD, + FontWeight::Black => FC_WEIGHT_BLACK, + FontWeight::ExtraBlack => FC_WEIGHT_EXTRABLACK, + } +} + +pub fn to_fc_width(width: FontWidth) -> c_int { + match width { + FontWidth::UltraCondensed => FC_WIDTH_ULTRACONDENSED, + FontWidth::ExtraCondensed => FC_WIDTH_EXTRACONDENSED, + FontWidth::Condensed => FC_WIDTH_CONDENSED, + FontWidth::SemiCondensed => FC_WIDTH_SEMICONDENSED, + FontWidth::Normal => FC_WIDTH_NORMAL, + FontWidth::SemiExpanded => FC_WIDTH_SEMIEXPANDED, + FontWidth::Expanded => FC_WIDTH_EXPANDED, + FontWidth::ExtraExpanded => FC_WIDTH_EXTRAEXPANDED, + FontWidth::UltraExpanded => FC_WIDTH_ULTRAEXPANDED, + } +} diff --git a/wezterm-font/src/lib.rs b/wezterm-font/src/lib.rs index eab0ea55c..8c7a9645c 100644 --- a/wezterm-font/src/lib.rs +++ b/wezterm-font/src/lib.rs @@ -3,7 +3,9 @@ use crate::locator::{new_locator, FontDataHandle, FontLocator}; use crate::rasterizer::{new_rasterizer, FontRasterizer}; use crate::shaper::{new_shaper, FontShaper}; use anyhow::{Context, Error}; -use config::{configuration, ConfigHandle, FontRasterizerSelection, TextStyle}; +use config::{ + configuration, ConfigHandle, FontRasterizerSelection, FontWeight, FontWidth, TextStyle, +}; use std::cell::RefCell; use std::collections::{HashMap, HashSet}; use std::rc::{Rc, Weak}; @@ -281,8 +283,11 @@ impl FontConfigInner { for attr in &attributes { if !attr.is_synthetic && !attr.is_fallback && !loaded.contains(attr) { - let styled_extra = if attr.bold || attr.italic { - ". A bold or italic variant of the font was requested; \ + let styled_extra = if attr.weight != FontWeight::default() + || attr.italic + || attr.width != FontWidth::default() + { + ". An alternative variant of the font was requested; \ TrueType and OpenType fonts don't have an automatic way to \ produce these font variants, so a separate font file containing \ the bold or italic variant must be installed" diff --git a/wezterm-font/src/locator/font_config.rs b/wezterm-font/src/locator/font_config.rs index 1185791df..88bc00891 100644 --- a/wezterm-font/src/locator/font_config.rs +++ b/wezterm-font/src/locator/font_config.rs @@ -3,7 +3,7 @@ use crate::locator::{FontDataHandle, FontDataSource, FontLocator}; use crate::parser::FontMatch; use anyhow::Context; use config::FontAttributes; -use fcwrap::{CharSet, Pattern as FontPattern}; +use fcwrap::{to_fc_weight, to_fc_width, CharSet, Pattern as FontPattern}; use std::collections::HashSet; use std::convert::TryInto; @@ -28,8 +28,16 @@ impl FontLocator for FontConfigFontLocator { let mut pattern = FontPattern::new()?; let start = std::time::Instant::now(); pattern.family(&attr.family)?; - pattern.add_integer("weight", if attr.bold { 200 } else { 80 })?; - pattern.add_integer("slant", if attr.italic { 100 } else { 0 })?; + pattern.add_integer("weight", to_fc_weight(attr.weight))?; + pattern.add_integer("width", to_fc_width(attr.width))?; + pattern.add_integer( + "slant", + if attr.italic { + fcwrap::FC_SLANT_ITALIC + } else { + fcwrap::FC_SLANT_ROMAN + }, + )?; pattern.add_integer("spacing", spacing)?; log::trace!("fc pattern before config subst: {:?}", pattern); @@ -44,10 +52,13 @@ impl FontLocator for FontConfigFontLocator { ); let file = best.get_file()?; + let index = best.get_integer("index")? as u32; + let variation = index >> 16; + let index = index & 0xffff; let handle = FontDataHandle { source: FontDataSource::OnDisk(file.into()), - index: best.get_integer("index")?.try_into()?, - variation: 0, + index, + variation, }; // fontconfig will give us a boatload of random fallbacks. diff --git a/wezterm-font/src/locator/gdi.rs b/wezterm-font/src/locator/gdi.rs index 404a435a9..b57f6ead3 100644 --- a/wezterm-font/src/locator/gdi.rs +++ b/wezterm-font/src/locator/gdi.rs @@ -2,7 +2,7 @@ use crate::locator::{FontDataHandle, FontDataSource, FontLocator}; use crate::parser::{parse_and_collect_font_info, rank_matching_fonts, FontMatch, ParsedFont}; -use config::FontAttributes; +use config::{FontAttributes, FontWeight as WTFontWeight, FontWidth}; use dwrote::{FontDescriptor, FontStretch, FontStyle, FontWeight}; use std::borrow::Cow; use std::collections::HashSet; @@ -88,7 +88,7 @@ fn load_font(font_attr: &FontAttributes) -> anyhow::Result { lfWidth: 0, lfEscapement: 0, lfOrientation: 0, - lfWeight: if font_attr.bold { 700 } else { 0 }, + lfWeight: font_attr.weight.to_opentype_weight() as _, lfItalic: if font_attr.italic { 1 } else { 0 }, lfUnderline: 0, lfStrikeOut: 0, @@ -122,11 +122,7 @@ fn load_font(font_attr: &FontAttributes) -> anyhow::Result { fn attributes_to_descriptor(font_attr: &FontAttributes) -> FontDescriptor { FontDescriptor { family_name: font_attr.family.to_string(), - weight: if font_attr.bold { - FontWeight::Bold - } else { - FontWeight::Regular - }, + weight: FontWeight::from_u32(font_attr.weight.to_opentype_weight() as u32), stretch: FontStretch::Normal, style: if font_attr.italic { FontStyle::Italic @@ -282,20 +278,8 @@ impl FontLocator for GdiFontLocator { ); let attr = FontAttributes { - bold: match font.weight() { - FontWeight::Thin - | FontWeight::ExtraLight - | FontWeight::Light - | FontWeight::SemiLight - | FontWeight::Regular - | FontWeight::Medium => false, - FontWeight::SemiBold - | FontWeight::Bold - | FontWeight::ExtraBold - | FontWeight::Black - | FontWeight::ExtraBlack => true, - FontWeight::Unknown(n) => n > 80, - }, + weight: WTFontWeight::from_opentype_weight(font.weight().to_u32() as _), + width: FontWidth::from_opentype_width(font.stretch().to_u32() as _), italic: false, family: font.family_name(), is_fallback: true, diff --git a/wezterm-font/src/parser.rs b/wezterm-font/src/parser.rs index 480776ab2..ea1fb2e35 100644 --- a/wezterm-font/src/parser.rs +++ b/wezterm-font/src/parser.rs @@ -1,6 +1,7 @@ use crate::locator::{FontDataHandle, FontDataSource}; use crate::shaper::GlyphInfo; use config::FontAttributes; +pub use config::{FontWeight, FontWidth}; use std::borrow::Cow; #[derive(Debug)] @@ -9,100 +10,6 @@ pub enum MaybeShaped { Unresolved { raw: String, slice_start: usize }, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FontWidth { - UltraCondensed, - ExtraCondensed, - Condensed, - SemiCondensed, - Normal, - SemiExpanded, - Expanded, - ExtraExpanded, - UltraExpanded, -} - -impl FontWidth { - pub fn from_opentype_width(w: u16) -> Self { - match w { - 1 => Self::UltraCondensed, - 2 => Self::ExtraCondensed, - 3 => Self::Condensed, - 4 => Self::SemiCondensed, - 5 => Self::Normal, - 6 => Self::SemiExpanded, - 7 => Self::Expanded, - 8 => Self::ExtraExpanded, - 9 => Self::UltraExpanded, - _ if w < 1 => Self::UltraCondensed, - _ => Self::UltraExpanded, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FontWeight { - Thin, - ExtraLight, - Light, - DemiLight, - Book, - Regular, - Medium, - DemiBold, - Bold, - ExtraBold, - Black, - ExtraBlack, -} - -impl FontWeight { - pub fn from_opentype_weight(w: u16) -> Self { - if w >= 1000 { - Self::ExtraBlack - } else if w >= 900 { - Self::Black - } else if w >= 800 { - Self::ExtraBold - } else if w >= 700 { - Self::Bold - } else if w >= 600 { - Self::DemiBold - } else if w >= 500 { - Self::Medium - } else if w >= 400 { - Self::Regular - } else if w >= 380 { - Self::Book - } else if w >= 350 { - Self::DemiLight - } else if w >= 300 { - Self::Light - } else if w >= 200 { - Self::ExtraLight - } else { - Self::Thin - } - } - - pub fn to_opentype_weight(self) -> u16 { - match self { - Self::Thin => 100, - Self::ExtraLight => 200, - Self::Light => 300, - Self::DemiLight => 350, - Self::Book => 380, - Self::Regular => 400, - Self::Medium => 500, - Self::DemiBold => 600, - Self::Bold => 700, - Self::ExtraBold => 800, - Self::Black => 900, - Self::ExtraBlack => 1000, - } - } -} - /// Represents a parsed font #[derive(Debug)] pub struct ParsedFont { @@ -183,14 +90,8 @@ impl ParsedFont { pub fn matches_attributes(&self, attr: &FontAttributes) -> FontMatch { if let Some(fam) = self.names.family.as_ref() { if attr.family == *fam { - let wanted_width = FontWidth::Normal; - if wanted_width == self.width { - let wanted_weight = if attr.bold { - FontWeight::Bold - } else { - FontWeight::Regular - } - .to_opentype_weight(); + if attr.width == self.width { + let wanted_weight = attr.weight.to_opentype_weight(); let weight = self.weight.to_opentype_weight(); if weight >= wanted_weight {