slint/sixtyfps_runtime/rendering_backends/gl/fonts.rs
2021-04-13 15:49:32 +02:00

207 lines
7.2 KiB
Rust

/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
SPDX-License-Identifier: GPL-3.0-only
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
use super::CanvasRc;
#[cfg(target_os = "windows")]
use font_kit::loader::Loader;
use sixtyfps_corelib::graphics::FontRequest;
#[cfg(target_arch = "wasm32")]
use std::cell::Cell;
use std::cell::RefCell;
thread_local! {
/// Database used to keep track of fonts added by the application
static APPLICATION_FONTS: RefCell<fontdb::Database> = RefCell::new(fontdb::Database::new())
}
#[cfg(target_arch = "wasm32")]
thread_local! {
static WASM_FONT_REGISTERED: Cell<bool> = Cell::new(false)
}
/// This function can be used to register a custom TrueType font with SixtyFPS,
/// for use with the `font-family` property. The provided slice must be a valid TrueType
/// font.
pub fn register_font_from_memory(data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
APPLICATION_FONTS.with(|fontdb| fontdb.borrow_mut().load_font_data(data.into()));
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
pub fn register_font_from_path(path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
let requested_path = path.canonicalize().unwrap_or_else(|_| path.to_owned());
APPLICATION_FONTS.with(|fontdb| {
for face_info in fontdb.borrow().faces() {
match &*face_info.source {
fontdb::Source::Binary(_) => {}
fontdb::Source::File(loaded_path) => {
if *loaded_path == requested_path {
return Ok(());
}
}
}
}
fontdb.borrow_mut().load_font_file(requested_path).map_err(|e| e.into())
})
}
#[cfg(target_arch = "wasm32")]
pub fn register_font_from_path(_path: &std::path::Path) -> Result<(), Box<dyn std::error::Error>> {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Registering fonts from paths is not supported in WASM builds",
)
.into());
}
pub(crate) fn try_load_app_font(
canvas: &CanvasRc,
request: &FontRequest,
) -> Option<femtovg::FontId> {
let family = request
.family
.as_ref()
.map_or(fontdb::Family::SansSerif, |family| fontdb::Family::Name(&family));
let query = fontdb::Query {
families: &[family],
weight: fontdb::Weight(request.weight.unwrap() as u16),
..Default::default()
};
APPLICATION_FONTS.with(|font_db| {
let font_db = font_db.borrow();
font_db.query(&query).and_then(|id| {
font_db.with_face_data(id, |data, _index| {
// pass index to femtovg once femtovg/femtovg/pull/21 is merged
canvas.borrow_mut().add_font_mem(&data).unwrap()
})
})
})
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn load_system_font(canvas: &CanvasRc, request: &FontRequest) -> femtovg::FontId {
let family_name =
request.family.as_ref().map_or(font_kit::family_name::FamilyName::SansSerif, |family| {
font_kit::family_name::FamilyName::Title(family.to_string())
});
let handle = font_kit::source::SystemSource::new()
.select_best_match(
&[family_name, font_kit::family_name::FamilyName::SansSerif],
&font_kit::properties::Properties::new()
.weight(font_kit::properties::Weight(request.weight.unwrap() as f32)),
)
.unwrap();
// pass index to femtovg once femtovg/femtovg/pull/21 is merged
match handle {
font_kit::handle::Handle::Path { path, font_index: _ } => {
canvas.borrow_mut().add_font(path)
}
font_kit::handle::Handle::Memory { bytes, font_index: _ } => {
canvas.borrow_mut().add_font_mem(bytes.as_slice())
}
}
.unwrap()
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn load_system_font(canvas: &CanvasRc, request: &FontRequest) -> femtovg::FontId {
WASM_FONT_REGISTERED.with(|registered| {
if !registered.get() {
registered.set(true);
register_font_from_memory(include_bytes!("fonts/DejaVuSans.ttf")).unwrap();
}
});
let mut fallback_request = request.clone();
fallback_request.family = Some("DejaVu Sans".into());
try_load_app_font(canvas, &fallback_request).unwrap()
}
#[cfg(target_os = "macos")]
pub(crate) fn font_fallbacks_for_request(
_request: &FontRequest,
_reference_text: &str,
) -> Vec<FontRequest> {
_request
.family
.as_ref()
.and_then(|family| {
core_text::font::new_from_name(&family, _request.pixel_size.unwrap_or_default() as f64)
.ok()
})
.map(|requested_font| {
core_text::font::cascade_list_for_languages(
&requested_font,
&core_foundation::array::CFArray::from_CFTypes(&[]),
)
.iter()
.map(|fallback_descriptor| FontRequest {
family: Some(fallback_descriptor.family_name().into()),
weight: _request.weight,
pixel_size: _request.pixel_size,
letter_spacing: _request.letter_spacing,
})
.filter(|fallback| !fallback.family.as_ref().unwrap().starts_with(".")) // font-kit asserts when loading `.Apple Fallback`
.take(1) // Take only the top from the fallback list until we mmap the llaaarge font files
.collect::<Vec<_>>()
})
.unwrap_or_default()
}
#[cfg(target_os = "windows")]
pub(crate) fn font_fallbacks_for_request(
_request: &FontRequest,
_reference_text: &str,
) -> Vec<FontRequest> {
let family_name =
_request.family.as_ref().map_or(font_kit::family_name::FamilyName::SansSerif, |family| {
font_kit::family_name::FamilyName::Title(family.to_string())
});
let handle = font_kit::source::SystemSource::new()
.select_best_match(
&[family_name, font_kit::family_name::FamilyName::SansSerif],
&font_kit::properties::Properties::new()
.weight(font_kit::properties::Weight(_request.weight.unwrap() as f32)),
)
.unwrap()
.load()
.unwrap();
handle
.get_fallbacks(_reference_text, "")
.fonts
.iter()
.map(|fallback_font| FontRequest {
family: Some(fallback_font.font.family_name().into()),
weight: _request.weight,
pixel_size: _request.pixel_size,
letter_spacing: _request.letter_spacing,
})
.collect()
}
#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
pub(crate) fn font_fallbacks_for_request(
_request: &FontRequest,
_reference_text: &str,
) -> Vec<FontRequest> {
vec![
#[cfg(target_arch = "wasm32")]
FontRequest {
family: Some("DejaVu Sans".into()),
weight: _request.weight,
pixel_size: _request.pixel_size,
letter_spacing: _request.letter_spacing,
},
]
}