mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-22 09:59:19 +00:00
4700d33728
See https://zed.dev/channel/gpui-536 Fixes https://github.com/zed-industries/zed/issues/9010 Fixes https://github.com/zed-industries/zed/issues/8883 Fixes https://github.com/zed-industries/zed/issues/8640 Fixes https://github.com/zed-industries/zed/issues/8598 Fixes https://github.com/zed-industries/zed/issues/8579 Fixes https://github.com/zed-industries/zed/issues/8363 Fixes https://github.com/zed-industries/zed/issues/8207 ### Problem After transitioning Zed to GPUI 2, we started noticing that interacting with the mouse on many UI elements would lead to a pretty annoying flicker. The main issue with the old approach was that hover state was calculated based on the previous frame. That is, when computing whether a given element was hovered in the current frame, we would use information about the same element in the previous frame. However, inspecting the previous frame tells us very little about what should be hovered in the current frame, as elements in the current frame may have changed significantly. ### Solution This pull request's main contribution is the introduction of a new `after_layout` phase when redrawing the window. The key idea is that we'll give every element a chance to register a hitbox (see `ElementContext::insert_hitbox`) before painting anything. Then, during the `paint` phase, elements can determine whether they're the topmost and draw their hover state accordingly. We are also removing the ability to give an arbitrary z-index to elements. Instead, we will follow the much simpler painter's algorithm. That is, an element that gets painted after will be drawn on top of an element that got painted earlier. Elements can still escape their current "stacking context" by using the new `ElementContext::defer_draw` method (see `Overlay` for an example). Elements drawn using this method will still be logically considered as being children of their original parent (for keybinding, focus and cache invalidation purposes) but their layout and paint passes will be deferred until the currently-drawn element is done. With these changes we also reworked geometry batching within the `Scene`. The new approach uses an AABB tree to determine geometry occlusion, which allows the GPU to render non-overlapping geometry in parallel. ### Performance Performance is slightly better than on `main` even though this new approach is more correct and we're maintaining an extra data structure (the AABB tree). ![before_after](https://github.com/zed-industries/zed/assets/482957/c8120b07-1dbd-4776-834a-d040e569a71e) Release Notes: - Fixed a bug that was causing popovers to flicker. --------- Co-authored-by: Nathan Sobo <nathan@zed.dev> Co-authored-by: Thorsten <thorsten@zed.dev>
777 lines
26 KiB
Rust
777 lines
26 KiB
Rust
mod font_features;
|
|
mod line;
|
|
mod line_layout;
|
|
mod line_wrapper;
|
|
|
|
pub use font_features::*;
|
|
pub use line::*;
|
|
pub use line_layout::*;
|
|
pub use line_wrapper::*;
|
|
|
|
use crate::{
|
|
px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
|
|
StrikethroughStyle, UnderlineStyle,
|
|
};
|
|
use anyhow::anyhow;
|
|
use collections::{BTreeSet, FxHashMap};
|
|
use core::fmt;
|
|
use derive_more::Deref;
|
|
use itertools::Itertools;
|
|
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
|
use smallvec::{smallvec, SmallVec};
|
|
use std::{
|
|
borrow::Cow,
|
|
cmp,
|
|
fmt::{Debug, Display, Formatter},
|
|
hash::{Hash, Hasher},
|
|
ops::{Deref, DerefMut, Range},
|
|
sync::Arc,
|
|
};
|
|
|
|
/// An opaque identifier for a specific font.
|
|
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
|
|
#[repr(C)]
|
|
pub struct FontId(pub usize);
|
|
|
|
/// An opaque identifier for a specific font family.
|
|
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
|
|
pub struct FontFamilyId(pub usize);
|
|
|
|
pub(crate) const SUBPIXEL_VARIANTS: u8 = 4;
|
|
|
|
/// The GPUI text rendering sub system.
|
|
pub struct TextSystem {
|
|
platform_text_system: Arc<dyn PlatformTextSystem>,
|
|
font_ids_by_font: RwLock<FxHashMap<Font, Result<FontId>>>,
|
|
font_metrics: RwLock<FxHashMap<FontId, FontMetrics>>,
|
|
raster_bounds: RwLock<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
|
|
wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,
|
|
font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
|
|
fallback_font_stack: SmallVec<[Font; 2]>,
|
|
}
|
|
|
|
impl TextSystem {
|
|
pub(crate) fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
|
|
TextSystem {
|
|
platform_text_system,
|
|
font_metrics: RwLock::default(),
|
|
raster_bounds: RwLock::default(),
|
|
font_ids_by_font: RwLock::default(),
|
|
wrapper_pool: Mutex::default(),
|
|
font_runs_pool: Mutex::default(),
|
|
fallback_font_stack: smallvec![
|
|
// TODO: This is currently Zed-specific.
|
|
// We should allow GPUI users to provide their own fallback font stack.
|
|
font("Zed Mono"),
|
|
font("Helvetica"),
|
|
font("Cantarell"), // Gnome
|
|
font("Ubuntu"), // Gnome (Ubuntu)
|
|
font("Noto Sans"), // KDE
|
|
],
|
|
}
|
|
}
|
|
|
|
/// Get a list of all available font names from the operating system.
|
|
pub fn all_font_names(&self) -> Vec<String> {
|
|
let mut names: BTreeSet<_> = self
|
|
.platform_text_system
|
|
.all_font_names()
|
|
.into_iter()
|
|
.collect();
|
|
names.extend(self.platform_text_system.all_font_families());
|
|
names.extend(
|
|
self.fallback_font_stack
|
|
.iter()
|
|
.map(|font| font.family.to_string()),
|
|
);
|
|
names.into_iter().collect()
|
|
}
|
|
|
|
/// Add a font's data to the text system.
|
|
pub fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
|
|
self.platform_text_system.add_fonts(fonts)
|
|
}
|
|
|
|
/// Get the FontId for the configure font family and style.
|
|
pub fn font_id(&self, font: &Font) -> Result<FontId> {
|
|
fn clone_font_id_result(font_id: &Result<FontId>) -> Result<FontId> {
|
|
match font_id {
|
|
Ok(font_id) => Ok(*font_id),
|
|
Err(err) => Err(anyhow!("{}", err)),
|
|
}
|
|
}
|
|
|
|
let font_id = self
|
|
.font_ids_by_font
|
|
.read()
|
|
.get(font)
|
|
.map(clone_font_id_result);
|
|
if let Some(font_id) = font_id {
|
|
font_id
|
|
} else {
|
|
let font_id = self.platform_text_system.font_id(font);
|
|
self.font_ids_by_font
|
|
.write()
|
|
.insert(font.clone(), clone_font_id_result(&font_id));
|
|
font_id
|
|
}
|
|
}
|
|
|
|
/// Resolves the specified font, falling back to the default font stack if
|
|
/// the font fails to load.
|
|
///
|
|
/// # Panics
|
|
///
|
|
/// Panics if the font and none of the fallbacks can be resolved.
|
|
pub fn resolve_font(&self, font: &Font) -> FontId {
|
|
if let Ok(font_id) = self.font_id(font) {
|
|
return font_id;
|
|
}
|
|
for fallback in &self.fallback_font_stack {
|
|
if let Ok(font_id) = self.font_id(fallback) {
|
|
return font_id;
|
|
}
|
|
}
|
|
|
|
panic!(
|
|
"failed to resolve font '{}' or any of the fallbacks: {}",
|
|
font.family,
|
|
self.fallback_font_stack
|
|
.iter()
|
|
.map(|fallback| &fallback.family)
|
|
.join(", ")
|
|
);
|
|
}
|
|
|
|
/// Get the bounding box for the given font and font size.
|
|
/// A font's bounding box is the smallest rectangle that could enclose all glyphs
|
|
/// in the font. superimposed over one another.
|
|
pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds<Pixels> {
|
|
self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
|
|
}
|
|
|
|
/// Get the typographic bounds for the given character, in the given font and size.
|
|
pub fn typographic_bounds(
|
|
&self,
|
|
font_id: FontId,
|
|
font_size: Pixels,
|
|
character: char,
|
|
) -> Result<Bounds<Pixels>> {
|
|
let glyph_id = self
|
|
.platform_text_system
|
|
.glyph_for_char(font_id, character)
|
|
.ok_or_else(|| anyhow!("glyph not found for character '{}'", character))?;
|
|
let bounds = self
|
|
.platform_text_system
|
|
.typographic_bounds(font_id, glyph_id)?;
|
|
Ok(self.read_metrics(font_id, |metrics| {
|
|
(bounds / metrics.units_per_em as f32 * font_size.0).map(px)
|
|
}))
|
|
}
|
|
|
|
/// Get the advance width for the given character, in the given font and size.
|
|
pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
|
|
let glyph_id = self
|
|
.platform_text_system
|
|
.glyph_for_char(font_id, ch)
|
|
.ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
|
|
let result = self.platform_text_system.advance(font_id, glyph_id)?
|
|
/ self.units_per_em(font_id) as f32;
|
|
|
|
Ok(result * font_size)
|
|
}
|
|
|
|
/// Get the number of font size units per 'em square',
|
|
/// Per MDN: "an abstract square whose height is the intended distance between
|
|
/// lines of type in the same type size"
|
|
pub fn units_per_em(&self, font_id: FontId) -> u32 {
|
|
self.read_metrics(font_id, |metrics| metrics.units_per_em)
|
|
}
|
|
|
|
/// Get the height of a capital letter in the given font and size.
|
|
pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
|
self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
|
|
}
|
|
|
|
/// Get the height of the x character in the given font and size.
|
|
pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
|
self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
|
|
}
|
|
|
|
/// Get the recommended distance from the baseline for the given font
|
|
pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
|
self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
|
|
}
|
|
|
|
/// Get the recommended distance below the baseline for the given font,
|
|
/// in single spaced text.
|
|
pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
|
|
self.read_metrics(font_id, |metrics| metrics.descent(font_size))
|
|
}
|
|
|
|
/// Get the recommended baseline offset for the given font and line height.
|
|
pub fn baseline_offset(
|
|
&self,
|
|
font_id: FontId,
|
|
font_size: Pixels,
|
|
line_height: Pixels,
|
|
) -> Pixels {
|
|
let ascent = self.ascent(font_id, font_size);
|
|
let descent = self.descent(font_id, font_size);
|
|
let padding_top = (line_height - ascent - descent) / 2.;
|
|
padding_top + ascent
|
|
}
|
|
|
|
fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> T {
|
|
let lock = self.font_metrics.upgradable_read();
|
|
|
|
if let Some(metrics) = lock.get(&font_id) {
|
|
read(metrics)
|
|
} else {
|
|
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
|
|
let metrics = lock
|
|
.entry(font_id)
|
|
.or_insert_with(|| self.platform_text_system.font_metrics(font_id));
|
|
read(metrics)
|
|
}
|
|
}
|
|
|
|
/// Returns a handle to a line wrapper, for the given font and font size.
|
|
pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
|
|
let lock = &mut self.wrapper_pool.lock();
|
|
let font_id = self.resolve_font(&font);
|
|
let wrappers = lock
|
|
.entry(FontIdWithSize { font_id, font_size })
|
|
.or_default();
|
|
let wrapper = wrappers.pop().unwrap_or_else(|| {
|
|
LineWrapper::new(font_id, font_size, self.platform_text_system.clone())
|
|
});
|
|
|
|
LineWrapperHandle {
|
|
wrapper: Some(wrapper),
|
|
text_system: self.clone(),
|
|
}
|
|
}
|
|
|
|
/// Get the rasterized size and location of a specific, rendered glyph.
|
|
pub(crate) fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
|
let raster_bounds = self.raster_bounds.upgradable_read();
|
|
if let Some(bounds) = raster_bounds.get(params) {
|
|
Ok(*bounds)
|
|
} else {
|
|
let mut raster_bounds = RwLockUpgradableReadGuard::upgrade(raster_bounds);
|
|
let bounds = self.platform_text_system.glyph_raster_bounds(params)?;
|
|
raster_bounds.insert(params.clone(), bounds);
|
|
Ok(bounds)
|
|
}
|
|
}
|
|
|
|
pub(crate) fn rasterize_glyph(
|
|
&self,
|
|
params: &RenderGlyphParams,
|
|
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
|
let raster_bounds = self.raster_bounds(params)?;
|
|
self.platform_text_system
|
|
.rasterize_glyph(params, raster_bounds)
|
|
}
|
|
}
|
|
|
|
/// The GPUI text layout subsystem.
|
|
#[derive(Deref)]
|
|
pub struct WindowTextSystem {
|
|
line_layout_cache: LineLayoutCache,
|
|
#[deref]
|
|
text_system: Arc<TextSystem>,
|
|
}
|
|
|
|
impl WindowTextSystem {
|
|
pub(crate) fn new(text_system: Arc<TextSystem>) -> Self {
|
|
Self {
|
|
line_layout_cache: LineLayoutCache::new(text_system.platform_text_system.clone()),
|
|
text_system,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn layout_index(&self) -> LineLayoutIndex {
|
|
self.line_layout_cache.layout_index()
|
|
}
|
|
|
|
pub(crate) fn reuse_layouts(&self, index: Range<LineLayoutIndex>) {
|
|
self.line_layout_cache.reuse_layouts(index)
|
|
}
|
|
|
|
/// Shape the given line, at the given font_size, for painting to the screen.
|
|
/// Subsets of the line can be styled independently with the `runs` parameter.
|
|
///
|
|
/// Note that this method can only shape a single line of text. It will panic
|
|
/// if the text contains newlines. If you need to shape multiple lines of text,
|
|
/// use `TextLayout::shape_text` instead.
|
|
pub fn shape_line(
|
|
&self,
|
|
text: SharedString,
|
|
font_size: Pixels,
|
|
runs: &[TextRun],
|
|
) -> Result<ShapedLine> {
|
|
debug_assert!(
|
|
text.find('\n').is_none(),
|
|
"text argument should not contain newlines"
|
|
);
|
|
|
|
let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
|
|
for run in runs {
|
|
if let Some(last_run) = decoration_runs.last_mut() {
|
|
if last_run.color == run.color
|
|
&& last_run.underline == run.underline
|
|
&& last_run.strikethrough == run.strikethrough
|
|
&& last_run.background_color == run.background_color
|
|
{
|
|
last_run.len += run.len as u32;
|
|
continue;
|
|
}
|
|
}
|
|
decoration_runs.push(DecorationRun {
|
|
len: run.len as u32,
|
|
color: run.color,
|
|
background_color: run.background_color,
|
|
underline: run.underline,
|
|
strikethrough: run.strikethrough,
|
|
});
|
|
}
|
|
|
|
let layout = self.layout_line(text.as_ref(), font_size, runs)?;
|
|
|
|
Ok(ShapedLine {
|
|
layout,
|
|
text,
|
|
decoration_runs,
|
|
})
|
|
}
|
|
|
|
/// Shape a multi line string of text, at the given font_size, for painting to the screen.
|
|
/// Subsets of the text can be styled independently with the `runs` parameter.
|
|
/// If `wrap_width` is provided, the line breaks will be adjusted to fit within the given width.
|
|
pub fn shape_text(
|
|
&self,
|
|
text: SharedString,
|
|
font_size: Pixels,
|
|
runs: &[TextRun],
|
|
wrap_width: Option<Pixels>,
|
|
) -> Result<SmallVec<[WrappedLine; 1]>> {
|
|
let mut runs = runs.iter().cloned().peekable();
|
|
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
|
|
|
|
let mut lines = SmallVec::new();
|
|
let mut line_start = 0;
|
|
|
|
let mut process_line = |line_text: SharedString| {
|
|
let line_end = line_start + line_text.len();
|
|
|
|
let mut last_font: Option<Font> = None;
|
|
let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
|
|
let mut run_start = line_start;
|
|
while run_start < line_end {
|
|
let Some(run) = runs.peek_mut() else {
|
|
break;
|
|
};
|
|
|
|
let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start;
|
|
|
|
if last_font == Some(run.font.clone()) {
|
|
font_runs.last_mut().unwrap().len += run_len_within_line;
|
|
} else {
|
|
last_font = Some(run.font.clone());
|
|
font_runs.push(FontRun {
|
|
len: run_len_within_line,
|
|
font_id: self.resolve_font(&run.font),
|
|
});
|
|
}
|
|
|
|
if decoration_runs.last().map_or(false, |last_run| {
|
|
last_run.color == run.color
|
|
&& last_run.underline == run.underline
|
|
&& last_run.strikethrough == run.strikethrough
|
|
&& last_run.background_color == run.background_color
|
|
}) {
|
|
decoration_runs.last_mut().unwrap().len += run_len_within_line as u32;
|
|
} else {
|
|
decoration_runs.push(DecorationRun {
|
|
len: run_len_within_line as u32,
|
|
color: run.color,
|
|
background_color: run.background_color,
|
|
underline: run.underline,
|
|
strikethrough: run.strikethrough,
|
|
});
|
|
}
|
|
|
|
if run_len_within_line == run.len {
|
|
runs.next();
|
|
} else {
|
|
// Preserve the remainder of the run for the next line
|
|
run.len -= run_len_within_line;
|
|
}
|
|
run_start += run_len_within_line;
|
|
}
|
|
|
|
let layout = self
|
|
.line_layout_cache
|
|
.layout_wrapped_line(&line_text, font_size, &font_runs, wrap_width);
|
|
|
|
lines.push(WrappedLine {
|
|
layout,
|
|
decoration_runs,
|
|
text: line_text,
|
|
});
|
|
|
|
// Skip `\n` character.
|
|
line_start = line_end + 1;
|
|
if let Some(run) = runs.peek_mut() {
|
|
run.len = run.len.saturating_sub(1);
|
|
if run.len == 0 {
|
|
runs.next();
|
|
}
|
|
}
|
|
|
|
font_runs.clear();
|
|
};
|
|
|
|
let mut split_lines = text.split('\n');
|
|
let mut processed = false;
|
|
|
|
if let Some(first_line) = split_lines.next() {
|
|
if let Some(second_line) = split_lines.next() {
|
|
processed = true;
|
|
process_line(first_line.to_string().into());
|
|
process_line(second_line.to_string().into());
|
|
for line_text in split_lines {
|
|
process_line(line_text.to_string().into());
|
|
}
|
|
}
|
|
}
|
|
|
|
if !processed {
|
|
process_line(text);
|
|
}
|
|
|
|
self.font_runs_pool.lock().push(font_runs);
|
|
|
|
Ok(lines)
|
|
}
|
|
|
|
pub(crate) fn finish_frame(&self) {
|
|
self.line_layout_cache.finish_frame()
|
|
}
|
|
|
|
/// Layout the given line of text, at the given font_size.
|
|
/// Subsets of the line can be styled independently with the `runs` parameter.
|
|
/// Generally, you should prefer to use `TextLayout::shape_line` instead, which
|
|
/// can be painted directly.
|
|
pub fn layout_line(
|
|
&self,
|
|
text: &str,
|
|
font_size: Pixels,
|
|
runs: &[TextRun],
|
|
) -> Result<Arc<LineLayout>> {
|
|
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
|
|
for run in runs.iter() {
|
|
let font_id = self.resolve_font(&run.font);
|
|
if let Some(last_run) = font_runs.last_mut() {
|
|
if last_run.font_id == font_id {
|
|
last_run.len += run.len;
|
|
continue;
|
|
}
|
|
}
|
|
font_runs.push(FontRun {
|
|
len: run.len,
|
|
font_id,
|
|
});
|
|
}
|
|
|
|
let layout = self
|
|
.line_layout_cache
|
|
.layout_line(text, font_size, &font_runs);
|
|
|
|
font_runs.clear();
|
|
self.font_runs_pool.lock().push(font_runs);
|
|
|
|
Ok(layout)
|
|
}
|
|
}
|
|
|
|
#[derive(Hash, Eq, PartialEq)]
|
|
struct FontIdWithSize {
|
|
font_id: FontId,
|
|
font_size: Pixels,
|
|
}
|
|
|
|
/// A handle into the text system, which can be used to compute the wrapped layout of text
|
|
pub struct LineWrapperHandle {
|
|
wrapper: Option<LineWrapper>,
|
|
text_system: Arc<TextSystem>,
|
|
}
|
|
|
|
impl Drop for LineWrapperHandle {
|
|
fn drop(&mut self) {
|
|
let mut state = self.text_system.wrapper_pool.lock();
|
|
let wrapper = self.wrapper.take().unwrap();
|
|
state
|
|
.get_mut(&FontIdWithSize {
|
|
font_id: wrapper.font_id,
|
|
font_size: wrapper.font_size,
|
|
})
|
|
.unwrap()
|
|
.push(wrapper);
|
|
}
|
|
}
|
|
|
|
impl Deref for LineWrapperHandle {
|
|
type Target = LineWrapper;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
self.wrapper.as_ref().unwrap()
|
|
}
|
|
}
|
|
|
|
impl DerefMut for LineWrapperHandle {
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
self.wrapper.as_mut().unwrap()
|
|
}
|
|
}
|
|
|
|
/// The degree of blackness or stroke thickness of a font. This value ranges from 100.0 to 900.0,
|
|
/// with 400.0 as normal.
|
|
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
|
pub struct FontWeight(pub f32);
|
|
|
|
impl Default for FontWeight {
|
|
#[inline]
|
|
fn default() -> FontWeight {
|
|
FontWeight::NORMAL
|
|
}
|
|
}
|
|
|
|
impl Hash for FontWeight {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
state.write_u32(u32::from_be_bytes(self.0.to_be_bytes()));
|
|
}
|
|
}
|
|
|
|
impl Eq for FontWeight {}
|
|
|
|
impl FontWeight {
|
|
/// Thin weight (100), the thinnest value.
|
|
pub const THIN: FontWeight = FontWeight(100.0);
|
|
/// Extra light weight (200).
|
|
pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
|
|
/// Light weight (300).
|
|
pub const LIGHT: FontWeight = FontWeight(300.0);
|
|
/// Normal (400).
|
|
pub const NORMAL: FontWeight = FontWeight(400.0);
|
|
/// Medium weight (500, higher than normal).
|
|
pub const MEDIUM: FontWeight = FontWeight(500.0);
|
|
/// Semibold weight (600).
|
|
pub const SEMIBOLD: FontWeight = FontWeight(600.0);
|
|
/// Bold weight (700).
|
|
pub const BOLD: FontWeight = FontWeight(700.0);
|
|
/// Extra-bold weight (800).
|
|
pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
|
|
/// Black weight (900), the thickest value.
|
|
pub const BLACK: FontWeight = FontWeight(900.0);
|
|
}
|
|
|
|
/// Allows italic or oblique faces to be selected.
|
|
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Default)]
|
|
pub enum FontStyle {
|
|
/// A face that is neither italic not obliqued.
|
|
#[default]
|
|
Normal,
|
|
/// A form that is generally cursive in nature.
|
|
Italic,
|
|
/// A typically-sloped version of the regular face.
|
|
Oblique,
|
|
}
|
|
|
|
impl Display for FontStyle {
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
Debug::fmt(self, f)
|
|
}
|
|
}
|
|
|
|
/// A styled run of text, for use in [`TextLayout`].
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct TextRun {
|
|
/// A number of utf8 bytes
|
|
pub len: usize,
|
|
/// The font to use for this run.
|
|
pub font: Font,
|
|
/// The color
|
|
pub color: Hsla,
|
|
/// The background color (if any)
|
|
pub background_color: Option<Hsla>,
|
|
/// The underline style (if any)
|
|
pub underline: Option<UnderlineStyle>,
|
|
/// The strikethrough style (if any)
|
|
pub strikethrough: Option<StrikethroughStyle>,
|
|
}
|
|
|
|
/// An identifier for a specific glyph, as returned by [`TextSystem::layout_line`].
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
|
#[repr(C)]
|
|
pub struct GlyphId(pub(crate) u32);
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub(crate) struct RenderGlyphParams {
|
|
pub(crate) font_id: FontId,
|
|
pub(crate) glyph_id: GlyphId,
|
|
pub(crate) font_size: Pixels,
|
|
pub(crate) subpixel_variant: Point<u8>,
|
|
pub(crate) scale_factor: f32,
|
|
pub(crate) is_emoji: bool,
|
|
}
|
|
|
|
impl Eq for RenderGlyphParams {}
|
|
|
|
impl Hash for RenderGlyphParams {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
self.font_id.0.hash(state);
|
|
self.glyph_id.0.hash(state);
|
|
self.font_size.0.to_bits().hash(state);
|
|
self.subpixel_variant.hash(state);
|
|
self.scale_factor.to_bits().hash(state);
|
|
}
|
|
}
|
|
|
|
/// The parameters for rendering an emoji glyph.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct RenderEmojiParams {
|
|
pub(crate) font_id: FontId,
|
|
pub(crate) glyph_id: GlyphId,
|
|
pub(crate) font_size: Pixels,
|
|
pub(crate) scale_factor: f32,
|
|
}
|
|
|
|
impl Eq for RenderEmojiParams {}
|
|
|
|
impl Hash for RenderEmojiParams {
|
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
self.font_id.0.hash(state);
|
|
self.glyph_id.0.hash(state);
|
|
self.font_size.0.to_bits().hash(state);
|
|
self.scale_factor.to_bits().hash(state);
|
|
}
|
|
}
|
|
|
|
/// The configuration details for identifying a specific font.
|
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
|
pub struct Font {
|
|
/// The font family name.
|
|
pub family: SharedString,
|
|
|
|
/// The font features to use.
|
|
pub features: FontFeatures,
|
|
|
|
/// The font weight.
|
|
pub weight: FontWeight,
|
|
|
|
/// The font style.
|
|
pub style: FontStyle,
|
|
}
|
|
|
|
/// Get a [`Font`] for a given name.
|
|
pub fn font(family: impl Into<SharedString>) -> Font {
|
|
Font {
|
|
family: family.into(),
|
|
features: FontFeatures::default(),
|
|
weight: FontWeight::default(),
|
|
style: FontStyle::default(),
|
|
}
|
|
}
|
|
|
|
impl Font {
|
|
/// Set this Font to be bold
|
|
pub fn bold(mut self) -> Self {
|
|
self.weight = FontWeight::BOLD;
|
|
self
|
|
}
|
|
|
|
/// Set this Font to be italic
|
|
pub fn italic(mut self) -> Self {
|
|
self.style = FontStyle::Italic;
|
|
self
|
|
}
|
|
}
|
|
|
|
/// A struct for storing font metrics.
|
|
/// It is used to define the measurements of a typeface.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct FontMetrics {
|
|
/// The number of font units that make up the "em square",
|
|
/// a scalable grid for determining the size of a typeface.
|
|
pub(crate) units_per_em: u32,
|
|
|
|
/// The vertical distance from the baseline of the font to the top of the glyph covers.
|
|
pub(crate) ascent: f32,
|
|
|
|
/// The vertical distance from the baseline of the font to the bottom of the glyph covers.
|
|
pub(crate) descent: f32,
|
|
|
|
/// The recommended additional space to add between lines of type.
|
|
pub(crate) line_gap: f32,
|
|
|
|
/// The suggested position of the underline.
|
|
pub(crate) underline_position: f32,
|
|
|
|
/// The suggested thickness of the underline.
|
|
pub(crate) underline_thickness: f32,
|
|
|
|
/// The height of a capital letter measured from the baseline of the font.
|
|
pub(crate) cap_height: f32,
|
|
|
|
/// The height of a lowercase x.
|
|
pub(crate) x_height: f32,
|
|
|
|
/// The outer limits of the area that the font covers.
|
|
/// Corresponds to the xMin / xMax / yMin / yMax values in the OpenType `head` table
|
|
pub(crate) bounding_box: Bounds<f32>,
|
|
}
|
|
|
|
impl FontMetrics {
|
|
/// Returns the vertical distance from the baseline of the font to the top of the glyph covers in pixels.
|
|
pub fn ascent(&self, font_size: Pixels) -> Pixels {
|
|
Pixels((self.ascent / self.units_per_em as f32) * font_size.0)
|
|
}
|
|
|
|
/// Returns the vertical distance from the baseline of the font to the bottom of the glyph covers in pixels.
|
|
pub fn descent(&self, font_size: Pixels) -> Pixels {
|
|
Pixels((self.descent / self.units_per_em as f32) * font_size.0)
|
|
}
|
|
|
|
/// Returns the recommended additional space to add between lines of type in pixels.
|
|
pub fn line_gap(&self, font_size: Pixels) -> Pixels {
|
|
Pixels((self.line_gap / self.units_per_em as f32) * font_size.0)
|
|
}
|
|
|
|
/// Returns the suggested position of the underline in pixels.
|
|
pub fn underline_position(&self, font_size: Pixels) -> Pixels {
|
|
Pixels((self.underline_position / self.units_per_em as f32) * font_size.0)
|
|
}
|
|
|
|
/// Returns the suggested thickness of the underline in pixels.
|
|
pub fn underline_thickness(&self, font_size: Pixels) -> Pixels {
|
|
Pixels((self.underline_thickness / self.units_per_em as f32) * font_size.0)
|
|
}
|
|
|
|
/// Returns the height of a capital letter measured from the baseline of the font in pixels.
|
|
pub fn cap_height(&self, font_size: Pixels) -> Pixels {
|
|
Pixels((self.cap_height / self.units_per_em as f32) * font_size.0)
|
|
}
|
|
|
|
/// Returns the height of a lowercase x in pixels.
|
|
pub fn x_height(&self, font_size: Pixels) -> Pixels {
|
|
Pixels((self.x_height / self.units_per_em as f32) * font_size.0)
|
|
}
|
|
|
|
/// Returns the outer limits of the area that the font covers in pixels.
|
|
pub fn bounding_box(&self, font_size: Pixels) -> Bounds<Pixels> {
|
|
(self.bounding_box / self.units_per_em as f32 * font_size.0).map(px)
|
|
}
|
|
}
|