2018-02-21 07:20:37 +03:00
|
|
|
use failure::Error;
|
2019-03-14 17:58:48 +03:00
|
|
|
use serde_derive::*;
|
2019-02-18 23:52:31 +03:00
|
|
|
mod ftfont;
|
2018-02-23 08:07:17 +03:00
|
|
|
mod hbwrap;
|
|
|
|
use self::hbwrap as harfbuzz;
|
2018-02-21 01:32:28 +03:00
|
|
|
|
2018-02-07 20:23:24 +03:00
|
|
|
use std::cell::RefCell;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::rc::Rc;
|
2019-03-04 18:49:03 +03:00
|
|
|
use std::sync::Arc;
|
2018-02-21 07:20:37 +03:00
|
|
|
|
2018-02-21 06:53:50 +03:00
|
|
|
pub mod system;
|
2018-02-21 07:20:37 +03:00
|
|
|
pub use self::system::*;
|
2018-01-17 10:32:23 +03:00
|
|
|
|
2018-02-21 07:55:03 +03:00
|
|
|
pub mod ftwrap;
|
|
|
|
|
2019-03-23 19:28:40 +03:00
|
|
|
#[cfg(all(unix, any(feature = "force-fontconfig", not(target_os = "macos"))))]
|
2018-02-21 07:55:03 +03:00
|
|
|
pub mod fcwrap;
|
2019-03-23 19:28:40 +03:00
|
|
|
#[cfg(all(unix, any(feature = "force-fontconfig", not(target_os = "macos"))))]
|
2018-02-21 07:55:03 +03:00
|
|
|
pub mod fontconfigandfreetype;
|
2018-01-17 10:32:23 +03:00
|
|
|
|
2019-02-19 09:02:31 +03:00
|
|
|
#[cfg(target_os = "macos")]
|
2018-02-21 09:33:09 +03:00
|
|
|
pub mod coretext;
|
|
|
|
|
2019-03-23 19:28:40 +03:00
|
|
|
#[cfg(any(target_os = "macos", windows))]
|
2019-02-19 00:28:25 +03:00
|
|
|
pub mod fontloader;
|
2019-03-23 19:28:40 +03:00
|
|
|
#[cfg(any(target_os = "macos", windows))]
|
2019-02-19 08:09:54 +03:00
|
|
|
pub mod fontloader_and_freetype;
|
2019-02-17 02:21:28 +03:00
|
|
|
|
2018-02-07 20:23:24 +03:00
|
|
|
use super::config::{Config, TextStyle};
|
|
|
|
use term::CellAttributes;
|
|
|
|
|
2018-03-04 00:00:56 +03:00
|
|
|
type FontPtr = Rc<RefCell<Box<NamedFont>>>;
|
|
|
|
|
2018-02-07 20:23:24 +03:00
|
|
|
/// Matches and loads fonts for a given input style
|
|
|
|
pub struct FontConfiguration {
|
2019-03-04 18:49:03 +03:00
|
|
|
config: Arc<Config>,
|
2018-03-04 00:00:56 +03:00
|
|
|
fonts: RefCell<HashMap<TextStyle, FontPtr>>,
|
2019-02-24 04:05:46 +03:00
|
|
|
system: Rc<FontSystem>,
|
2019-02-23 00:34:48 +03:00
|
|
|
metrics: RefCell<Option<FontMetrics>>,
|
2019-02-23 01:52:35 +03:00
|
|
|
dpi_scale: RefCell<f64>,
|
|
|
|
font_scale: RefCell<f64>,
|
2019-02-19 09:02:31 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize, Clone, Copy)]
|
|
|
|
pub enum FontSystemSelection {
|
|
|
|
FontConfigAndFreeType,
|
|
|
|
FontLoaderAndFreeType,
|
|
|
|
CoreText,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for FontSystemSelection {
|
|
|
|
fn default() -> Self {
|
2019-03-23 23:45:26 +03:00
|
|
|
if cfg!(all(unix, not(target_os = "macos"),)) {
|
2019-02-19 09:02:31 +03:00
|
|
|
FontSystemSelection::FontConfigAndFreeType
|
2019-03-23 07:14:37 +03:00
|
|
|
/*
|
2019-02-23 02:51:36 +03:00
|
|
|
} else if cfg!(target_os = "macos") {
|
|
|
|
FontSystemSelection::CoreText
|
2019-03-23 07:14:37 +03:00
|
|
|
*/
|
2019-02-23 02:51:36 +03:00
|
|
|
} else {
|
|
|
|
FontSystemSelection::FontLoaderAndFreeType
|
2019-02-19 09:02:31 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 19:05:42 +03:00
|
|
|
thread_local! {
|
|
|
|
static DEFAULT_FONT_SYSTEM: RefCell<FontSystemSelection> = RefCell::new(Default::default());
|
|
|
|
}
|
|
|
|
|
2019-02-19 09:02:31 +03:00
|
|
|
impl FontSystemSelection {
|
2019-02-24 21:33:21 +03:00
|
|
|
fn new_font_system(self) -> Rc<FontSystem> {
|
2019-02-19 09:02:31 +03:00
|
|
|
match self {
|
|
|
|
FontSystemSelection::FontConfigAndFreeType => {
|
2019-03-23 19:28:40 +03:00
|
|
|
#[cfg(all(unix, any(feature = "force-fontconfig", not(target_os = "macos"))))]
|
2019-02-24 04:05:46 +03:00
|
|
|
return Rc::new(fontconfigandfreetype::FontSystemImpl::new());
|
2019-03-23 19:28:40 +03:00
|
|
|
#[cfg(not(all(
|
|
|
|
unix,
|
|
|
|
any(feature = "force-fontconfig", not(target_os = "macos"))
|
|
|
|
)))]
|
2019-02-19 09:02:31 +03:00
|
|
|
panic!("fontconfig not compiled in");
|
|
|
|
}
|
|
|
|
FontSystemSelection::FontLoaderAndFreeType => {
|
2019-03-23 19:28:40 +03:00
|
|
|
#[cfg(any(target_os = "macos", windows))]
|
|
|
|
return Rc::new(fontloader_and_freetype::FontSystemImpl::new());
|
|
|
|
#[cfg(not(any(target_os = "macos", windows)))]
|
|
|
|
panic!("font-loader not compiled in");
|
2019-02-19 09:02:31 +03:00
|
|
|
}
|
|
|
|
FontSystemSelection::CoreText => {
|
|
|
|
#[cfg(target_os = "macos")]
|
2019-02-24 04:05:46 +03:00
|
|
|
return Rc::new(coretext::FontSystemImpl::new());
|
2019-02-19 09:02:31 +03:00
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
|
|
panic!("coretext not compiled in");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-20 20:01:36 +03:00
|
|
|
pub fn variants() -> Vec<&'static str> {
|
|
|
|
vec![
|
|
|
|
"FontConfigAndFreeType",
|
|
|
|
"FontLoaderAndFreeType",
|
|
|
|
"FontLoaderAndRustType",
|
|
|
|
"CoreText",
|
|
|
|
]
|
|
|
|
}
|
2019-03-04 19:05:42 +03:00
|
|
|
|
|
|
|
pub fn set_default(self) {
|
|
|
|
DEFAULT_FONT_SYSTEM.with(|def| {
|
|
|
|
*def.borrow_mut() = self;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_default() -> Self {
|
|
|
|
DEFAULT_FONT_SYSTEM.with(|def| *def.borrow())
|
|
|
|
}
|
2019-02-19 09:02:31 +03:00
|
|
|
}
|
|
|
|
|
2019-02-20 20:01:36 +03:00
|
|
|
impl std::str::FromStr for FontSystemSelection {
|
|
|
|
type Err = Error;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
match s.to_lowercase().as_ref() {
|
|
|
|
"fontconfigandfreetype" => Ok(FontSystemSelection::FontConfigAndFreeType),
|
2019-02-23 02:51:36 +03:00
|
|
|
"fontloaderandfreetype" => Ok(FontSystemSelection::FontLoaderAndFreeType),
|
2019-02-20 20:01:36 +03:00
|
|
|
"coretext" => Ok(FontSystemSelection::CoreText),
|
|
|
|
_ => Err(format_err!(
|
|
|
|
"{} is not a valid FontSystemSelection variant, possible values are {:?}",
|
|
|
|
s,
|
|
|
|
FontSystemSelection::variants()
|
|
|
|
)),
|
|
|
|
}
|
|
|
|
}
|
2018-02-07 20:23:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl FontConfiguration {
|
|
|
|
/// Create a new empty configuration
|
2019-03-04 18:49:03 +03:00
|
|
|
pub fn new(config: Arc<Config>, system: FontSystemSelection) -> Self {
|
2018-02-07 20:23:24 +03:00
|
|
|
Self {
|
|
|
|
config,
|
|
|
|
fonts: RefCell::new(HashMap::new()),
|
2019-02-20 20:01:36 +03:00
|
|
|
system: system.new_font_system(),
|
2019-02-23 00:34:48 +03:00
|
|
|
metrics: RefCell::new(None),
|
2019-02-23 01:52:35 +03:00
|
|
|
font_scale: RefCell::new(1.0),
|
|
|
|
dpi_scale: RefCell::new(1.0),
|
2018-02-07 20:23:24 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Given a text style, load (with caching) the font that best
|
|
|
|
/// matches according to the fontconfig pattern.
|
2018-02-21 06:53:50 +03:00
|
|
|
pub fn cached_font(&self, style: &TextStyle) -> Result<Rc<RefCell<Box<NamedFont>>>, Error> {
|
2018-02-07 20:23:24 +03:00
|
|
|
let mut fonts = self.fonts.borrow_mut();
|
|
|
|
|
|
|
|
if let Some(entry) = fonts.get(style) {
|
|
|
|
return Ok(Rc::clone(entry));
|
|
|
|
}
|
|
|
|
|
2019-02-23 01:52:35 +03:00
|
|
|
let scale = *self.dpi_scale.borrow() * *self.font_scale.borrow();
|
|
|
|
let font = Rc::new(RefCell::new(self.system.load_font(
|
|
|
|
&self.config,
|
|
|
|
style,
|
|
|
|
scale,
|
|
|
|
)?));
|
2018-02-07 20:23:24 +03:00
|
|
|
fonts.insert(style.clone(), Rc::clone(&font));
|
|
|
|
Ok(font)
|
|
|
|
}
|
|
|
|
|
2019-02-23 01:52:35 +03:00
|
|
|
pub fn change_scaling(&self, font_scale: f64, dpi_scale: f64) {
|
|
|
|
*self.dpi_scale.borrow_mut() = dpi_scale;
|
|
|
|
*self.font_scale.borrow_mut() = font_scale;
|
|
|
|
self.fonts.borrow_mut().clear();
|
|
|
|
self.metrics.borrow_mut().take();
|
|
|
|
}
|
|
|
|
|
2018-02-07 20:23:24 +03:00
|
|
|
/// Returns the baseline font specified in the configuration
|
2018-02-21 06:53:50 +03:00
|
|
|
pub fn default_font(&self) -> Result<Rc<RefCell<Box<NamedFont>>>, Error> {
|
2018-02-07 20:23:24 +03:00
|
|
|
self.cached_font(&self.config.font)
|
|
|
|
}
|
|
|
|
|
2019-02-23 01:52:35 +03:00
|
|
|
pub fn get_font_scale(&self) -> f64 {
|
|
|
|
*self.font_scale.borrow()
|
|
|
|
}
|
|
|
|
|
2019-02-24 10:07:20 +03:00
|
|
|
pub fn get_dpi_scale(&self) -> f64 {
|
|
|
|
*self.dpi_scale.borrow()
|
|
|
|
}
|
|
|
|
|
2019-02-23 00:34:48 +03:00
|
|
|
pub fn default_font_metrics(&self) -> Result<FontMetrics, Error> {
|
|
|
|
{
|
|
|
|
let metrics = self.metrics.borrow();
|
|
|
|
if let Some(metrics) = metrics.as_ref() {
|
|
|
|
return Ok(*metrics);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let font = self.default_font()?;
|
|
|
|
let metrics = font.borrow_mut().get_fallback(0)?.metrics();
|
|
|
|
|
2019-02-24 21:33:21 +03:00
|
|
|
*self.metrics.borrow_mut() = Some(metrics);
|
2019-02-23 00:34:48 +03:00
|
|
|
|
|
|
|
Ok(metrics)
|
|
|
|
}
|
|
|
|
|
2018-02-07 20:23:24 +03:00
|
|
|
/// Apply the defined font_rules from the user configuration to
|
|
|
|
/// produce the text style that best matches the supplied input
|
|
|
|
/// cell attributes.
|
|
|
|
pub fn match_style(&self, attrs: &CellAttributes) -> &TextStyle {
|
|
|
|
// a little macro to avoid boilerplate for matching the rules.
|
|
|
|
// If the rule doesn't specify a value for an attribute then
|
|
|
|
// it will implicitly match. If it specifies an attribute
|
|
|
|
// then it has to have the same value as that in the input attrs.
|
|
|
|
macro_rules! attr_match {
|
|
|
|
($ident:ident, $rule:expr) => {
|
|
|
|
if let Some($ident) = $rule.$ident {
|
|
|
|
if $ident != attrs.$ident() {
|
|
|
|
// Does not match
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// matches so far...
|
2018-04-11 23:15:12 +03:00
|
|
|
};
|
2018-02-07 20:23:24 +03:00
|
|
|
};
|
|
|
|
|
2018-03-04 00:00:56 +03:00
|
|
|
for rule in &self.config.font_rules {
|
2018-02-07 20:23:24 +03:00
|
|
|
attr_match!(intensity, &rule);
|
|
|
|
attr_match!(underline, &rule);
|
|
|
|
attr_match!(italic, &rule);
|
|
|
|
attr_match!(blink, &rule);
|
|
|
|
attr_match!(reverse, &rule);
|
|
|
|
attr_match!(strikethrough, &rule);
|
|
|
|
attr_match!(invisible, &rule);
|
|
|
|
|
|
|
|
// If we get here, then none of the rules didn't match,
|
|
|
|
// so we therefore assume that it did match overall.
|
|
|
|
return &rule.font;
|
|
|
|
}
|
|
|
|
&self.config.font
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-17 08:20:35 +03:00
|
|
|
#[allow(dead_code)]
|
2018-02-21 06:53:50 +03:00
|
|
|
pub fn shape_with_harfbuzz(
|
|
|
|
font: &mut NamedFont,
|
|
|
|
font_idx: system::FallbackIdx,
|
|
|
|
s: &str,
|
|
|
|
) -> Result<Vec<GlyphInfo>, Error> {
|
|
|
|
let features = vec![
|
|
|
|
// kerning
|
|
|
|
harfbuzz::feature_from_string("kern")?,
|
|
|
|
// ligatures
|
|
|
|
harfbuzz::feature_from_string("liga")?,
|
|
|
|
// contextual ligatures
|
|
|
|
harfbuzz::feature_from_string("clig")?,
|
|
|
|
];
|
|
|
|
|
|
|
|
let mut buf = harfbuzz::Buffer::new()?;
|
2019-03-23 19:28:40 +03:00
|
|
|
buf.set_script(harfbuzz::hb_script_t::HB_SCRIPT_LATIN);
|
|
|
|
buf.set_direction(harfbuzz::hb_direction_t::HB_DIRECTION_LTR);
|
2018-02-21 06:53:50 +03:00
|
|
|
buf.set_language(harfbuzz::language_from_string("en")?);
|
|
|
|
buf.add_str(s);
|
|
|
|
|
|
|
|
{
|
|
|
|
let fallback = font.get_fallback(font_idx)?;
|
|
|
|
fallback.harfbuzz_shape(&mut buf, Some(features.as_slice()));
|
2018-01-21 12:32:59 +03:00
|
|
|
}
|
|
|
|
|
2018-02-21 06:53:50 +03:00
|
|
|
let infos = buf.glyph_infos();
|
|
|
|
let positions = buf.glyph_positions();
|
|
|
|
|
|
|
|
let mut cluster = Vec::new();
|
|
|
|
|
|
|
|
let mut last_text_pos = None;
|
|
|
|
let mut first_fallback_pos = None;
|
|
|
|
|
|
|
|
// Compute the lengths of the text clusters.
|
|
|
|
// Ligatures and combining characters mean
|
|
|
|
// that a single glyph can take the place of
|
|
|
|
// multiple characters. The 'cluster' member
|
|
|
|
// of the glyph info is set to the position
|
|
|
|
// in the input utf8 text, so we make a pass
|
|
|
|
// over the set of clusters to look for differences
|
|
|
|
// greater than 1 and backfill the length of
|
|
|
|
// the corresponding text fragment. We need
|
|
|
|
// the fragments to properly handle fallback,
|
|
|
|
// and they're handy to have for debugging
|
|
|
|
// purposes too.
|
|
|
|
let mut sizes = Vec::with_capacity(s.len());
|
|
|
|
for (i, info) in infos.iter().enumerate() {
|
|
|
|
let pos = info.cluster as usize;
|
|
|
|
let mut size = 1;
|
2018-01-17 10:32:23 +03:00
|
|
|
if let Some(last_pos) = last_text_pos {
|
2018-02-21 06:53:50 +03:00
|
|
|
let diff = pos - last_pos;
|
2018-01-17 10:32:23 +03:00
|
|
|
if diff > 1 {
|
2018-02-21 06:53:50 +03:00
|
|
|
sizes[i - 1] = diff;
|
2018-01-17 10:32:23 +03:00
|
|
|
}
|
2018-02-21 06:53:50 +03:00
|
|
|
} else if pos != 0 {
|
|
|
|
size = pos;
|
2018-01-17 10:32:23 +03:00
|
|
|
}
|
2018-02-21 06:53:50 +03:00
|
|
|
last_text_pos = Some(pos);
|
|
|
|
sizes.push(size);
|
|
|
|
}
|
|
|
|
if let Some(last_pos) = last_text_pos {
|
|
|
|
let diff = s.len() - last_pos;
|
|
|
|
if diff > 1 {
|
|
|
|
let last = sizes.len() - 1;
|
|
|
|
sizes[last] = diff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//debug!("sizes: {:?}", sizes);
|
|
|
|
|
|
|
|
// Now make a second pass to determine if we need
|
|
|
|
// to perform fallback to a later font.
|
|
|
|
// We can determine this by looking at the codepoint.
|
|
|
|
for (i, info) in infos.iter().enumerate() {
|
|
|
|
let pos = info.cluster as usize;
|
|
|
|
if info.codepoint == 0 {
|
|
|
|
if first_fallback_pos.is_none() {
|
|
|
|
// Start of a run that needs fallback
|
|
|
|
first_fallback_pos = Some(pos);
|
|
|
|
}
|
2018-03-04 00:00:56 +03:00
|
|
|
} else if let Some(start_pos) = first_fallback_pos {
|
2018-02-21 06:53:50 +03:00
|
|
|
// End of a fallback run
|
|
|
|
//debug!("range: {:?}-{:?} needs fallback", start, pos);
|
2018-02-04 19:28:32 +03:00
|
|
|
|
2018-03-04 00:00:56 +03:00
|
|
|
let substr = &s[start_pos..pos];
|
2018-08-05 01:06:16 +03:00
|
|
|
let mut shape = match shape_with_harfbuzz(font, font_idx + 1, substr) {
|
|
|
|
Ok(shape) => Ok(shape),
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("{:?} for {:?}", e, substr);
|
2019-02-17 04:12:54 +03:00
|
|
|
if font_idx == 0 && s == "?" {
|
|
|
|
bail!("unable to find any usable glyphs for `?` in font_idx 0");
|
|
|
|
}
|
2018-08-05 01:06:16 +03:00
|
|
|
shape_with_harfbuzz(font, 0, "?")
|
|
|
|
}
|
|
|
|
}?;
|
2018-01-17 10:32:23 +03:00
|
|
|
|
2018-02-21 06:53:50 +03:00
|
|
|
// Fixup the cluster member to match our current offset
|
2018-03-04 00:00:56 +03:00
|
|
|
for mut info in &mut shape {
|
|
|
|
info.cluster += start_pos as u32;
|
2018-01-17 10:32:23 +03:00
|
|
|
}
|
2018-02-21 06:53:50 +03:00
|
|
|
cluster.append(&mut shape);
|
|
|
|
|
|
|
|
first_fallback_pos = None;
|
2018-01-17 10:32:23 +03:00
|
|
|
}
|
2018-02-21 06:53:50 +03:00
|
|
|
if info.codepoint != 0 {
|
2018-10-20 03:25:15 +03:00
|
|
|
if s.is_char_boundary(pos) && s.is_char_boundary(pos + sizes[i]) {
|
|
|
|
let text = &s[pos..pos + sizes[i]];
|
|
|
|
//debug!("glyph from `{}`", text);
|
|
|
|
cluster.push(GlyphInfo::new(text, font_idx, info, &positions[i]));
|
|
|
|
} else {
|
|
|
|
cluster.append(&mut shape_with_harfbuzz(font, 0, "?")?);
|
|
|
|
}
|
2018-02-21 06:53:50 +03:00
|
|
|
}
|
|
|
|
}
|
2018-01-17 10:32:23 +03:00
|
|
|
|
2018-02-21 06:53:50 +03:00
|
|
|
// Check to see if we started and didn't finish a
|
|
|
|
// fallback run.
|
2018-03-04 00:00:56 +03:00
|
|
|
if let Some(start_pos) = first_fallback_pos {
|
|
|
|
let substr = &s[start_pos..];
|
2018-02-21 06:53:50 +03:00
|
|
|
if false {
|
|
|
|
debug!(
|
2018-01-17 10:32:23 +03:00
|
|
|
"at end {:?}-{:?} needs fallback {}",
|
2018-03-04 00:00:56 +03:00
|
|
|
start_pos,
|
2018-01-17 10:32:23 +03:00
|
|
|
s.len() - 1,
|
|
|
|
substr,
|
|
|
|
);
|
|
|
|
}
|
2018-08-05 01:06:16 +03:00
|
|
|
let mut shape = match shape_with_harfbuzz(font, font_idx + 1, substr) {
|
|
|
|
Ok(shape) => Ok(shape),
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("{:?} for {:?}", e, substr);
|
2019-02-17 04:12:54 +03:00
|
|
|
if font_idx == 0 && s == "?" {
|
|
|
|
bail!("unable to find any usable glyphs for `?` in font_idx 0");
|
|
|
|
}
|
2018-08-05 01:06:16 +03:00
|
|
|
shape_with_harfbuzz(font, 0, "?")
|
|
|
|
}
|
|
|
|
}?;
|
2018-02-21 06:53:50 +03:00
|
|
|
// Fixup the cluster member to match our current offset
|
2018-03-04 00:00:56 +03:00
|
|
|
for mut info in &mut shape {
|
|
|
|
info.cluster += start_pos as u32;
|
2018-02-21 06:53:50 +03:00
|
|
|
}
|
|
|
|
cluster.append(&mut shape);
|
2018-01-17 10:32:23 +03:00
|
|
|
}
|
|
|
|
|
2018-02-21 06:53:50 +03:00
|
|
|
//debug!("shaped: {:#?}", cluster);
|
2018-01-21 12:32:59 +03:00
|
|
|
|
2018-02-21 06:53:50 +03:00
|
|
|
Ok(cluster)
|
2018-01-17 10:32:23 +03:00
|
|
|
}
|