Detailed changes
@@ -1,6 +1,6 @@
use super::{
event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
- MacWindow, TextSystem,
+ FontSystem, MacWindow,
};
use crate::{
executor,
@@ -488,7 +488,7 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
pub struct MacPlatform {
dispatcher: Arc<Dispatcher>,
- fonts: Arc<TextSystem>,
+ fonts: Arc<FontSystem>,
pasteboard: id,
text_hash_pasteboard_type: id,
metadata_pasteboard_type: id,
@@ -498,7 +498,7 @@ impl MacPlatform {
pub fn new() -> Self {
Self {
dispatcher: Arc::new(Dispatcher),
- fonts: Arc::new(TextSystem::new()),
+ fonts: Arc::new(FontSystem::new()),
pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
@@ -7,7 +7,7 @@ description = "The next version of Zed's GPU-accelerated UI framework"
publish = false
[features]
-test-support = ["backtrace", "dhat", "env_logger", "collections/test-support"]
+test = ["backtrace", "dhat", "env_logger", "collections/test-support"]
[lib]
path = "src/gpui3.rs"
@@ -1,6 +1,6 @@
use crate::{
- Context, FontCache, LayoutId, Platform, Reference, View, Window, WindowContext, WindowHandle,
- WindowId,
+ current_platform, Context, LayoutId, Platform, Reference, TextSystem, View, Window,
+ WindowContext, WindowHandle, WindowId,
};
use anyhow::{anyhow, Result};
use slotmap::SlotMap;
@@ -10,14 +10,25 @@ use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc, sync::Arc};
pub struct App(Rc<RefCell<AppContext>>);
impl App {
- pub fn new(platform: Rc<dyn Platform>) -> Self {
- Self(Rc::new(RefCell::new(AppContext::new(platform))))
+ pub fn new() -> Self {
+ Self(Rc::new(RefCell::new(AppContext::new(current_platform()))))
+ }
+
+ pub fn run<F>(self, on_finish_launching: F)
+ where
+ F: 'static + FnOnce(&mut AppContext),
+ {
+ let platform = self.0.borrow().platform().clone();
+ platform.run(Box::new(move || {
+ let mut cx = self.0.borrow_mut();
+ on_finish_launching(&mut *cx);
+ }));
}
}
pub struct AppContext {
platform: Rc<dyn Platform>,
- font_cache: Arc<FontCache>,
+ text_system: Arc<TextSystem>,
pub(crate) entities: SlotMap<EntityId, Option<Box<dyn Any>>>,
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
// We recycle this memory across layout requests.
@@ -26,10 +37,10 @@ pub struct AppContext {
impl AppContext {
pub fn new(platform: Rc<dyn Platform>) -> Self {
- let font_cache = Arc::new(FontCache::new(platform.text_system()));
+ let text_system = Arc::new(TextSystem::new(platform.text_system()));
AppContext {
platform,
- font_cache,
+ text_system,
entities: SlotMap::with_key(),
windows: SlotMap::with_key(),
layout_id_buffer: Default::default(),
@@ -45,8 +56,8 @@ impl AppContext {
&self.platform
}
- pub fn font_cache(&self) -> &Arc<FontCache> {
- &self.font_cache
+ pub fn text_system(&self) -> &Arc<TextSystem> {
+ &self.text_system
}
pub fn open_window<S: 'static>(
@@ -47,7 +47,7 @@ impl<S: 'static> Element for Div<S> {
cx.pop_text_style();
}
- Ok((cx.request_layout(style, children.clone())?, children))
+ Ok((cx.request_layout(style.into(), children.clone())?, children))
}
fn paint(
@@ -3,7 +3,6 @@ mod color;
mod element;
mod elements;
mod executor;
-mod fonts;
mod geometry;
mod platform;
mod renderer;
@@ -11,7 +10,7 @@ mod scene;
mod style;
mod styled;
mod taffy;
-mod text;
+mod text_system;
mod util;
mod window;
@@ -21,7 +20,6 @@ pub use color::*;
pub use element::*;
pub use elements::*;
pub use executor::*;
-pub use fonts::*;
pub use geometry::*;
pub use platform::*;
pub use refineable::*;
@@ -33,8 +31,7 @@ pub use style::*;
pub use styled::*;
pub use taffy::LayoutId;
use taffy::TaffyLayoutEngine;
-use text::*;
-pub use text::{Glyph, GlyphId};
+pub use text_system::*;
pub use util::arc_cow::ArcCow;
pub use window::*;
@@ -35,10 +35,10 @@ pub use mac::*;
#[cfg(any(test, feature = "test"))]
pub use test::*;
-// #[cfg(target_os = "macos")]
-// pub fn current() -> Rc<dyn Platform> {
-// MacPlatform
-// }
+#[cfg(target_os = "macos")]
+pub(crate) fn current_platform() -> Rc<dyn Platform> {
+ Rc::new(MacPlatform::new())
+}
pub trait Platform {
fn executor(&self) -> Rc<ForegroundExecutor>;
@@ -529,7 +529,7 @@ impl Platform for MacPlatform {
None
};
- if let Some(mut done_tx) = done_tx.take() {
+ if let Some(done_tx) = done_tx.take() {
let _ = done_tx.send(result);
}
});
@@ -557,7 +557,7 @@ impl Platform for MacPlatform {
}
}
- if let Some(mut done_tx) = done_tx.take() {
+ if let Some(done_tx) = done_tx.take() {
let _ = done_tx.send(result);
}
});
@@ -18,7 +18,11 @@ use core_graphics::{
};
use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
use font_kit::{
- handle::Handle, hinting::HintingOptions, metrics::Metrics, source::SystemSource,
+ handle::Handle,
+ hinting::HintingOptions,
+ metrics::Metrics,
+ properties::{Style as FontkitStyle, Weight as FontkitWeight},
+ source::SystemSource,
sources::mem::MemSource,
};
use parking_lot::RwLock;
@@ -187,8 +191,8 @@ impl TextSystemState {
let idx = font_kit::matching::find_best_match(
&candidates,
&font_kit::properties::Properties {
- style,
- weight,
+ style: style.into(),
+ weight: weight.into(),
stretch: Default::default(),
},
)?;
@@ -589,6 +593,22 @@ impl From<Vector2F> for Size<f32> {
}
}
+impl From<FontWeight> for FontkitWeight {
+ fn from(value: FontWeight) -> Self {
+ FontkitWeight(value.0)
+ }
+}
+
+impl From<FontStyle> for FontkitStyle {
+ fn from(style: FontStyle) -> Self {
+ match style {
+ FontStyle::Normal => FontkitStyle::Normal,
+ FontStyle::Italic => FontkitStyle::Italic,
+ FontStyle::Oblique => FontkitStyle::Oblique,
+ }
+ }
+}
+
// #[cfg(test)]
// mod tests {
// use super::*;
@@ -1379,13 +1379,13 @@ extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
}
extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
- unsafe {
- // let window_state = get_window_state(this);
- // let mut window_state = window_state.as_ref().borrow_mut();
- // if let Some(scene) = window_state.scene_to_render.take() {
- // window_state.renderer.render(&scene);
- // };
- }
+ // unsafe {
+ // let window_state = get_window_state(this);
+ // let mut window_state = window_state.as_ref().borrow_mut();
+ // if let Some(scene) = window_state.scene_to_render.take() {
+ // window_state.renderer.render(&scene);
+ // };
+ // }
}
extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
@@ -1,4 +1,4 @@
-use crate::{text::GlyphId, FontId};
+use crate::{FontId, GlyphId};
use super::{Bounds, Hsla, Pixels, Point};
use bytemuck::{Pod, Zeroable};
@@ -3,7 +3,7 @@ use super::{
Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, RunStyle, SharedString, Size,
SizeRefinement, ViewContext, WindowContext,
};
-use crate::FontCache;
+use crate::{FontCache, TextSystem};
use refineable::Refineable;
pub use taffy::style::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
@@ -114,7 +114,7 @@ pub struct TextStyle {
}
impl TextStyle {
- pub fn highlight(mut self, style: HighlightStyle, _font_cache: &FontCache) -> Result<Self> {
+ pub fn highlight(mut self, style: HighlightStyle) -> Result<Self> {
if let Some(weight) = style.font_weight {
self.font_weight = weight;
}
@@ -0,0 +1,326 @@
+mod font_cache;
+mod line_wrapper;
+mod text_layout_cache;
+
+pub use font_cache::*;
+use line_wrapper::*;
+use schemars::JsonSchema;
+use serde_derive::{Deserialize, Serialize};
+pub use text_layout_cache::*;
+
+use crate::{Hsla, Pixels, PlatformTextSystem, Point, Result, Size, UnderlineStyle};
+use collections::HashMap;
+use core::fmt;
+use parking_lot::Mutex;
+use std::{
+ fmt::{Debug, Display, Formatter},
+ hash::{Hash, Hasher},
+ ops::{Deref, DerefMut},
+ sync::Arc,
+};
+
+pub struct TextSystem {
+ font_cache: Arc<FontCache>,
+ text_layout_cache: Arc<TextLayoutCache>,
+ platform_text_system: Arc<dyn PlatformTextSystem>,
+ wrapper_pool: Mutex<HashMap<(FontId, Pixels), Vec<LineWrapper>>>,
+}
+
+impl TextSystem {
+ pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
+ TextSystem {
+ font_cache: Arc::new(FontCache::new(platform_text_system.clone())),
+ text_layout_cache: Arc::new(TextLayoutCache::new(platform_text_system.clone())),
+ platform_text_system,
+ wrapper_pool: Mutex::new(HashMap::default()),
+ }
+ }
+
+ pub fn font_family_name(&self, family_id: FontFamilyId) -> Result<Arc<str>> {
+ self.font_cache.family_name(family_id)
+ }
+
+ pub fn load_font_family(
+ &self,
+ names: &[&str],
+ features: &FontFeatures,
+ ) -> Result<FontFamilyId> {
+ self.font_cache.load_family(names, features)
+ }
+
+ /// Returns an arbitrary font family that is available on the system.
+ pub fn known_existing_font_family(&self) -> FontFamilyId {
+ self.font_cache.known_existing_family()
+ }
+
+ pub fn default_font(&self, family_id: FontFamilyId) -> FontId {
+ self.font_cache.default_font(family_id)
+ }
+
+ pub fn select_font(
+ &self,
+ family_id: FontFamilyId,
+ weight: FontWeight,
+ style: FontStyle,
+ ) -> Result<FontId> {
+ self.font_cache.select_font(family_id, weight, style)
+ }
+
+ pub fn read_font_metric<F, T>(&self, font_id: FontId, f: F) -> T
+ where
+ F: FnOnce(&FontMetrics) -> T,
+ T: 'static,
+ {
+ self.font_cache.read_metric(font_id, f)
+ }
+
+ pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Size<Pixels> {
+ self.font_cache.bounding_box(font_id, font_size)
+ }
+
+ pub fn em_width(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+ self.font_cache.em_width(font_id, font_size)
+ }
+
+ pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+ self.font_cache.em_advance(font_id, font_size)
+ }
+
+ pub fn line_height(&self, font_size: Pixels) -> Pixels {
+ self.font_cache.line_height(font_size)
+ }
+
+ pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+ self.font_cache.cap_height(font_id, font_size)
+ }
+
+ pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+ self.font_cache.x_height(font_id, font_size)
+ }
+
+ pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+ self.font_cache.ascent(font_id, font_size)
+ }
+
+ pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+ self.font_cache.descent(font_id, font_size)
+ }
+
+ pub fn em_size(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+ self.font_cache.em_size(font_id, font_size)
+ }
+
+ pub fn baseline_offset(&self, font_id: FontId, font_size: Pixels) -> Pixels {
+ self.font_cache.baseline_offset(font_id, font_size)
+ }
+
+ pub fn layout_str<'a>(
+ &'a self,
+ text: &'a str,
+ font_size: Pixels,
+ runs: &'a [(usize, RunStyle)],
+ ) -> Line {
+ self.text_layout_cache.layout_str(text, font_size, runs)
+ }
+
+ pub fn finish_frame(&self) {
+ self.text_layout_cache.finish_frame()
+ }
+
+ pub fn line_wrapper(self: &Arc<Self>, font_id: FontId, font_size: Pixels) -> LineWrapperHandle {
+ let lock = &mut self.wrapper_pool.lock();
+ let wrappers = lock.entry((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(),
+ }
+ }
+}
+
+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(&(wrapper.font_id, 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)]
+pub enum FontStyle {
+ /// A face that is neither italic not obliqued.
+ Normal,
+ /// A form that is generally cursive in nature.
+ Italic,
+ /// A typically-sloped version of the regular face.
+ Oblique,
+}
+
+impl Default for FontStyle {
+ fn default() -> FontStyle {
+ FontStyle::Normal
+ }
+}
+
+impl Display for FontStyle {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ Debug::fmt(self, f)
+ }
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
+pub struct FontFeatures {
+ pub calt: Option<bool>,
+ pub case: Option<bool>,
+ pub cpsp: Option<bool>,
+ pub frac: Option<bool>,
+ pub liga: Option<bool>,
+ pub onum: Option<bool>,
+ pub ordn: Option<bool>,
+ pub pnum: Option<bool>,
+ pub ss01: Option<bool>,
+ pub ss02: Option<bool>,
+ pub ss03: Option<bool>,
+ pub ss04: Option<bool>,
+ pub ss05: Option<bool>,
+ pub ss06: Option<bool>,
+ pub ss07: Option<bool>,
+ pub ss08: Option<bool>,
+ pub ss09: Option<bool>,
+ pub ss10: Option<bool>,
+ pub ss11: Option<bool>,
+ pub ss12: Option<bool>,
+ pub ss13: Option<bool>,
+ pub ss14: Option<bool>,
+ pub ss15: Option<bool>,
+ pub ss16: Option<bool>,
+ pub ss17: Option<bool>,
+ pub ss18: Option<bool>,
+ pub ss19: Option<bool>,
+ pub ss20: Option<bool>,
+ pub subs: Option<bool>,
+ pub sups: Option<bool>,
+ pub swsh: Option<bool>,
+ pub titl: Option<bool>,
+ pub tnum: Option<bool>,
+ pub zero: Option<bool>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct RunStyle {
+ pub color: Hsla,
+ pub font_id: FontId,
+ pub underline: Option<UnderlineStyle>,
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct GlyphId(u32);
+
+impl From<GlyphId> for u32 {
+ fn from(value: GlyphId) -> Self {
+ value.0
+ }
+}
+
+impl From<u16> for GlyphId {
+ fn from(num: u16) -> Self {
+ GlyphId(num as u32)
+ }
+}
+
+impl From<u32> for GlyphId {
+ fn from(num: u32) -> Self {
+ GlyphId(num)
+ }
+}
+
+#[derive(Clone, Debug)]
+pub struct Glyph {
+ pub id: GlyphId,
+ pub position: Point<Pixels>,
+ pub index: usize,
+ pub is_emoji: bool,
+}
+
+#[derive(Default, Debug)]
+pub struct LineLayout {
+ pub font_size: Pixels,
+ pub width: Pixels,
+ pub ascent: Pixels,
+ pub descent: Pixels,
+ pub runs: Vec<Run>,
+ pub len: usize,
+}
+
+#[derive(Debug)]
+pub struct Run {
+ pub font_id: FontId,
+ pub glyphs: Vec<Glyph>,
+}
@@ -1,16 +1,9 @@
-use crate::{px, Bounds, LineWrapper, Pixels, PlatformTextSystem, Result, Size};
-use anyhow::anyhow;
-pub use font_kit::properties::{
- Properties as FontProperties, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight,
+use crate::{
+ px, Bounds, FontFeatures, FontStyle, FontWeight, Pixels, PlatformTextSystem, Result, Size,
};
+use anyhow::anyhow;
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use std::{
- collections::HashMap,
- ops::{Deref, DerefMut},
- sync::Arc,
-};
+use std::{collections::HashMap, sync::Arc};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct FontFamilyId(usize);
@@ -18,73 +11,14 @@ pub struct FontFamilyId(usize);
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct FontId(pub usize);
-#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
-pub struct FontFeatures {
- pub calt: Option<bool>,
- pub case: Option<bool>,
- pub cpsp: Option<bool>,
- pub frac: Option<bool>,
- pub liga: Option<bool>,
- pub onum: Option<bool>,
- pub ordn: Option<bool>,
- pub pnum: Option<bool>,
- pub ss01: Option<bool>,
- pub ss02: Option<bool>,
- pub ss03: Option<bool>,
- pub ss04: Option<bool>,
- pub ss05: Option<bool>,
- pub ss06: Option<bool>,
- pub ss07: Option<bool>,
- pub ss08: Option<bool>,
- pub ss09: Option<bool>,
- pub ss10: Option<bool>,
- pub ss11: Option<bool>,
- pub ss12: Option<bool>,
- pub ss13: Option<bool>,
- pub ss14: Option<bool>,
- pub ss15: Option<bool>,
- pub ss16: Option<bool>,
- pub ss17: Option<bool>,
- pub ss18: Option<bool>,
- pub ss19: Option<bool>,
- pub ss20: Option<bool>,
- pub subs: Option<bool>,
- pub sups: Option<bool>,
- pub swsh: Option<bool>,
- pub titl: Option<bool>,
- pub tnum: Option<bool>,
- pub zero: Option<bool>,
-}
-
-#[allow(non_camel_case_types)]
-#[derive(Deserialize)]
-enum WeightJson {
- thin,
- extra_light,
- light,
- normal,
- medium,
- semibold,
- bold,
- extra_bold,
- black,
-}
+pub(crate) struct FontCache(RwLock<FontCacheState>);
-struct Family {
- name: Arc<str>,
- font_features: FontFeatures,
- font_ids: Vec<FontId>,
-}
-
-pub struct FontCache(RwLock<FontCacheState>);
-
-pub struct FontCacheState {
- font_system: Arc<dyn PlatformTextSystem>,
+pub(crate) struct FontCacheState {
+ platform_text_system: Arc<dyn PlatformTextSystem>,
families: Vec<Family>,
default_family: Option<FontFamilyId>,
font_selections: HashMap<FontFamilyId, HashMap<(FontWeight, FontStyle), FontId>>,
metrics: HashMap<FontId, FontMetrics>,
- wrapper_pool: HashMap<(FontId, Pixels), Vec<LineWrapper>>,
}
unsafe impl Send for FontCache {}
@@ -92,12 +26,11 @@ unsafe impl Send for FontCache {}
impl FontCache {
pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
Self(RwLock::new(FontCacheState {
- font_system: fonts,
+ platform_text_system: fonts,
families: Default::default(),
default_family: None,
font_selections: Default::default(),
metrics: Default::default(),
- wrapper_pool: Default::default(),
}))
}
@@ -124,14 +57,18 @@ impl FontCache {
let mut state = RwLockUpgradableReadGuard::upgrade(state);
- if let Ok(font_ids) = state.font_system.load_family(name, features) {
+ if let Ok(font_ids) = state.platform_text_system.load_family(name, features) {
if font_ids.is_empty() {
continue;
}
let family_id = FontFamilyId(state.families.len());
for font_id in &font_ids {
- if state.font_system.glyph_for_char(*font_id, 'm').is_none() {
+ if state
+ .platform_text_system
+ .glyph_for_char(*font_id, 'm')
+ .is_none()
+ {
return Err(anyhow!("font must contain a glyph for the 'm' character"));
}
}
@@ -162,7 +99,7 @@ impl FontCache {
&Default::default(),
)
.unwrap_or_else(|_| {
- let all_family_names = self.0.read().font_system.all_families();
+ let all_family_names = self.0.read().platform_text_system.all_families();
let all_family_names: Vec<_> = all_family_names
.iter()
.map(|string| string.as_str())
@@ -197,7 +134,7 @@ impl FontCache {
let mut inner = RwLockUpgradableReadGuard::upgrade(inner);
let family = &inner.families[family_id.0];
let font_id = inner
- .font_system
+ .platform_text_system
.select_font(&family.font_ids, weight, style)
.unwrap_or(family.font_ids[0]);
inner
@@ -209,7 +146,7 @@ impl FontCache {
}
}
- pub fn metric<F, T>(&self, font_id: FontId, f: F) -> T
+ pub fn read_metric<F, T>(&self, font_id: FontId, f: F) -> T
where
F: FnOnce(&FontMetrics) -> T,
T: 'static,
@@ -218,7 +155,7 @@ impl FontCache {
if let Some(metrics) = state.metrics.get(&font_id) {
f(metrics)
} else {
- let metrics = state.font_system.font_metrics(font_id);
+ let metrics = state.platform_text_system.font_metrics(font_id);
let metric = f(&metrics);
let mut state = RwLockUpgradableReadGuard::upgrade(state);
state.metrics.insert(font_id, metrics);
@@ -227,7 +164,7 @@ impl FontCache {
}
pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Size<Pixels> {
- let bounding_box = self.metric(font_id, |m| m.bounding_box);
+ let bounding_box = self.read_metric(font_id, |m| m.bounding_box);
let width = px(bounding_box.size.width) * self.em_size(font_id, font_size);
let height = px(bounding_box.size.height) * self.em_size(font_id, font_size);
@@ -239,9 +176,12 @@ impl FontCache {
let bounds;
{
let state = self.0.read();
- glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
+ glyph_id = state
+ .platform_text_system
+ .glyph_for_char(font_id, 'm')
+ .unwrap();
bounds = state
- .font_system
+ .platform_text_system
.typographic_bounds(font_id, glyph_id)
.unwrap();
}
@@ -253,8 +193,14 @@ impl FontCache {
let advance;
{
let state = self.0.read();
- glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
- advance = state.font_system.advance(font_id, glyph_id).unwrap();
+ glyph_id = state
+ .platform_text_system
+ .glyph_for_char(font_id, 'm')
+ .unwrap();
+ advance = state
+ .platform_text_system
+ .advance(font_id, glyph_id)
+ .unwrap();
}
self.em_size(font_id, font_size) * advance.width
}
@@ -264,23 +210,23 @@ impl FontCache {
}
pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
- self.em_size(font_id, font_size) * self.metric(font_id, |m| m.cap_height)
+ self.em_size(font_id, font_size) * self.read_metric(font_id, |m| m.cap_height)
}
pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Pixels {
- self.em_size(font_id, font_size) * self.metric(font_id, |m| m.x_height)
+ self.em_size(font_id, font_size) * self.read_metric(font_id, |m| m.x_height)
}
pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
- self.em_size(font_id, font_size) * self.metric(font_id, |m| m.ascent)
+ self.em_size(font_id, font_size) * self.read_metric(font_id, |m| m.ascent)
}
pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Pixels {
- self.em_size(font_id, font_size) * self.metric(font_id, |m| -m.descent)
+ self.em_size(font_id, font_size) * self.read_metric(font_id, |m| -m.descent)
}
pub fn em_size(&self, font_id: FontId, font_size: Pixels) -> Pixels {
- font_size / self.metric(font_id, |m| m.units_per_em as f32)
+ font_size / self.read_metric(font_id, |m| m.units_per_em as f32)
}
pub fn baseline_offset(&self, font_id: FontId, font_size: Pixels) -> Pixels {
@@ -290,49 +236,6 @@ impl FontCache {
let padding_top = (line_height - ascent - descent) / 2.;
padding_top + ascent
}
-
- pub fn line_wrapper(self: &Arc<Self>, font_id: FontId, font_size: Pixels) -> LineWrapperHandle {
- let mut state = self.0.write();
- let wrappers = state.wrapper_pool.entry((font_id, font_size)).or_default();
- let wrapper = wrappers
- .pop()
- .unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.font_system.clone()));
- LineWrapperHandle {
- wrapper: Some(wrapper),
- font_cache: self.clone(),
- }
- }
-}
-
-pub struct LineWrapperHandle {
- wrapper: Option<LineWrapper>,
- font_cache: Arc<FontCache>,
-}
-
-impl Drop for LineWrapperHandle {
- fn drop(&mut self) {
- let mut state = self.font_cache.0.write();
- let wrapper = self.wrapper.take().unwrap();
- state
- .wrapper_pool
- .get_mut(&(wrapper.font_id, 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()
- }
}
#[derive(Clone, Copy, Debug)]
@@ -348,6 +251,12 @@ pub struct FontMetrics {
pub bounding_box: Bounds<f32>,
}
+struct Family {
+ name: Arc<str>,
+ font_features: FontFeatures,
+ font_ids: Vec<FontId>,
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -0,0 +1,347 @@
+use super::FontId;
+use crate::{px, Line, Pixels, PlatformTextSystem, RunStyle, ShapedBoundary};
+use collections::HashMap;
+use std::{iter, sync::Arc};
+
+pub struct LineWrapper {
+ text_system: Arc<dyn PlatformTextSystem>,
+ pub(crate) font_id: FontId,
+ pub(crate) font_size: Pixels,
+ cached_ascii_char_widths: [Option<Pixels>; 128],
+ cached_other_char_widths: HashMap<char, Pixels>,
+}
+
+impl LineWrapper {
+ pub const MAX_INDENT: u32 = 256;
+
+ pub fn new(
+ font_id: FontId,
+ font_size: Pixels,
+ text_system: Arc<dyn PlatformTextSystem>,
+ ) -> Self {
+ Self {
+ text_system,
+ font_id,
+ font_size,
+ cached_ascii_char_widths: [None; 128],
+ cached_other_char_widths: HashMap::default(),
+ }
+ }
+
+ pub fn wrap_line<'a>(
+ &'a mut self,
+ line: &'a str,
+ wrap_width: Pixels,
+ ) -> impl Iterator<Item = Boundary> + 'a {
+ let mut width = px(0.);
+ let mut first_non_whitespace_ix = None;
+ let mut indent = None;
+ let mut last_candidate_ix = 0;
+ let mut last_candidate_width = px(0.);
+ let mut last_wrap_ix = 0;
+ let mut prev_c = '\0';
+ let mut char_indices = line.char_indices();
+ iter::from_fn(move || {
+ for (ix, c) in char_indices.by_ref() {
+ if c == '\n' {
+ continue;
+ }
+
+ if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
+ last_candidate_ix = ix;
+ last_candidate_width = width;
+ }
+
+ if c != ' ' && first_non_whitespace_ix.is_none() {
+ first_non_whitespace_ix = Some(ix);
+ }
+
+ let char_width = self.width_for_char(c);
+ width += char_width;
+ if width > wrap_width && ix > last_wrap_ix {
+ if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
+ {
+ indent = Some(
+ Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
+ );
+ }
+
+ if last_candidate_ix > 0 {
+ last_wrap_ix = last_candidate_ix;
+ width -= last_candidate_width;
+ last_candidate_ix = 0;
+ } else {
+ last_wrap_ix = ix;
+ width = char_width;
+ }
+
+ if let Some(indent) = indent {
+ width += self.width_for_char(' ') * indent as f32;
+ }
+
+ return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
+ }
+ prev_c = c;
+ }
+
+ None
+ })
+ }
+
+ pub fn wrap_shaped_line<'a>(
+ &'a mut self,
+ str: &'a str,
+ line: &'a Line,
+ wrap_width: Pixels,
+ ) -> impl Iterator<Item = ShapedBoundary> + 'a {
+ let mut first_non_whitespace_ix = None;
+ let mut last_candidate_ix = None;
+ let mut last_candidate_x = px(0.);
+ let mut last_wrap_ix = ShapedBoundary {
+ run_ix: 0,
+ glyph_ix: 0,
+ };
+ let mut last_wrap_x = px(0.);
+ let mut prev_c = '\0';
+ let mut glyphs = line
+ .runs()
+ .iter()
+ .enumerate()
+ .flat_map(move |(run_ix, run)| {
+ run.glyphs()
+ .iter()
+ .enumerate()
+ .map(move |(glyph_ix, glyph)| {
+ let character = str[glyph.index..].chars().next().unwrap();
+ (
+ ShapedBoundary { run_ix, glyph_ix },
+ character,
+ glyph.position.x,
+ )
+ })
+ })
+ .peekable();
+
+ iter::from_fn(move || {
+ while let Some((ix, c, x)) = glyphs.next() {
+ if c == '\n' {
+ continue;
+ }
+
+ if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
+ last_candidate_ix = Some(ix);
+ last_candidate_x = x;
+ }
+
+ if c != ' ' && first_non_whitespace_ix.is_none() {
+ first_non_whitespace_ix = Some(ix);
+ }
+
+ let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
+ let width = next_x - last_wrap_x;
+ if width > wrap_width && ix > last_wrap_ix {
+ if let Some(last_candidate_ix) = last_candidate_ix.take() {
+ last_wrap_ix = last_candidate_ix;
+ last_wrap_x = last_candidate_x;
+ } else {
+ last_wrap_ix = ix;
+ last_wrap_x = x;
+ }
+
+ return Some(last_wrap_ix);
+ }
+ prev_c = c;
+ }
+
+ None
+ })
+ }
+
+ fn is_boundary(&self, prev: char, next: char) -> bool {
+ (prev == ' ') && (next != ' ')
+ }
+
+ #[inline(always)]
+ fn width_for_char(&mut self, c: char) -> Pixels {
+ if (c as u32) < 128 {
+ if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
+ cached_width
+ } else {
+ let width = self.compute_width_for_char(c);
+ self.cached_ascii_char_widths[c as usize] = Some(width);
+ width
+ }
+ } else {
+ if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
+ *cached_width
+ } else {
+ let width = self.compute_width_for_char(c);
+ self.cached_other_char_widths.insert(c, width);
+ width
+ }
+ }
+ }
+
+ fn compute_width_for_char(&self, c: char) -> Pixels {
+ self.text_system
+ .layout_line(
+ &c.to_string(),
+ self.font_size,
+ &[(
+ 1,
+ RunStyle {
+ font_id: self.font_id,
+ color: Default::default(),
+ underline: Default::default(),
+ },
+ )],
+ )
+ .width
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct Boundary {
+ pub ix: usize,
+ pub next_indent: u32,
+}
+
+impl Boundary {
+ fn new(ix: usize, next_indent: u32) -> Self {
+ Self { ix, next_indent }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{AppContext, FontWeight};
+
+ #[test]
+ fn test_wrap_line() {
+ let cx = AppContext::test();
+
+ let text_system = cx.text_system().clone();
+ let family = text_system
+ .load_font_family(&["Courier"], &Default::default())
+ .unwrap();
+ let font_id = text_system
+ .select_font(family, Default::default(), Default::default())
+ .unwrap();
+
+ let mut wrapper =
+ LineWrapper::new(font_id, px(16.), text_system.platform_text_system.clone());
+ assert_eq!(
+ wrapper
+ .wrap_line("aa bbb cccc ddddd eeee", px(72.))
+ .collect::<Vec<_>>(),
+ &[
+ Boundary::new(7, 0),
+ Boundary::new(12, 0),
+ Boundary::new(18, 0)
+ ],
+ );
+ assert_eq!(
+ wrapper
+ .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
+ .collect::<Vec<_>>(),
+ &[
+ Boundary::new(4, 0),
+ Boundary::new(11, 0),
+ Boundary::new(18, 0)
+ ],
+ );
+ assert_eq!(
+ wrapper
+ .wrap_line(" aaaaaaa", px(72.))
+ .collect::<Vec<_>>(),
+ &[
+ Boundary::new(7, 5),
+ Boundary::new(9, 5),
+ Boundary::new(11, 5),
+ ]
+ );
+ assert_eq!(
+ wrapper
+ .wrap_line(" ", px(72.))
+ .collect::<Vec<_>>(),
+ &[
+ Boundary::new(7, 0),
+ Boundary::new(14, 0),
+ Boundary::new(21, 0)
+ ]
+ );
+ assert_eq!(
+ wrapper
+ .wrap_line(" aaaaaaaaaaaaaa", px(72.))
+ .collect::<Vec<_>>(),
+ &[
+ Boundary::new(7, 0),
+ Boundary::new(14, 3),
+ Boundary::new(18, 3),
+ Boundary::new(22, 3),
+ ]
+ );
+ }
+
+ // todo! repeat this test
+ #[test]
+ fn test_wrap_shaped_line() {
+ let cx = AppContext::test();
+ let text_system = cx.text_system().clone();
+
+ let family = text_system
+ .load_font_family(&["Helvetica"], &Default::default())
+ .unwrap();
+ let font_id = text_system
+ .select_font(family, Default::default(), Default::default())
+ .unwrap();
+ let normal = RunStyle {
+ font_id,
+ color: Default::default(),
+ underline: Default::default(),
+ };
+ let bold = RunStyle {
+ font_id: text_system
+ .select_font(family, FontWeight::BOLD, Default::default())
+ .unwrap(),
+ color: Default::default(),
+ underline: Default::default(),
+ };
+
+ let text = "aa bbb cccc ddddd eeee";
+ let line = text_system.layout_str(
+ text,
+ px(16.),
+ &[
+ (4, normal.clone()),
+ (5, bold.clone()),
+ (6, normal.clone()),
+ (1, bold),
+ (7, normal),
+ ],
+ );
+
+ let mut wrapper =
+ LineWrapper::new(font_id, px(16.), text_system.platform_text_system.clone());
+ assert_eq!(
+ wrapper
+ .wrap_shaped_line(text, &line, px(72.))
+ .collect::<Vec<_>>(),
+ &[
+ ShapedBoundary {
+ run_ix: 1,
+ glyph_ix: 3
+ },
+ ShapedBoundary {
+ run_ix: 2,
+ glyph_ix: 3
+ },
+ ShapedBoundary {
+ run_ix: 4,
+ glyph_ix: 2
+ }
+ ],
+ );
+ }
+}
@@ -1,7 +1,6 @@
-use crate::{black, px};
-
-use super::{
- point, Bounds, FontId, Hsla, Pixels, PlatformTextSystem, Point, UnderlineStyle, WindowContext,
+use crate::{
+ black, point, px, Bounds, FontId, Glyph, Hsla, LineLayout, Pixels, PlatformTextSystem, Point,
+ Run, RunStyle, UnderlineStyle, WindowContext,
};
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec;
@@ -9,52 +8,15 @@ use std::{
borrow::Borrow,
collections::HashMap,
hash::{Hash, Hasher},
- iter,
sync::Arc,
};
-pub struct TextLayoutCache {
+pub(crate) struct TextLayoutCache {
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
fonts: Arc<dyn PlatformTextSystem>,
}
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RunStyle {
- pub color: Hsla,
- pub font_id: FontId,
- pub underline: Option<UnderlineStyle>,
-}
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
-pub struct GlyphId(u32);
-
-impl From<GlyphId> for u32 {
- fn from(value: GlyphId) -> Self {
- value.0
- }
-}
-
-impl From<u16> for GlyphId {
- fn from(num: u16) -> Self {
- GlyphId(num as u32)
- }
-}
-
-impl From<u32> for GlyphId {
- fn from(num: u32) -> Self {
- GlyphId(num)
- }
-}
-
-#[derive(Clone, Debug)]
-pub struct Glyph {
- pub id: GlyphId,
- pub position: Point<Pixels>,
- pub index: usize,
- pub is_emoji: bool,
-}
-
impl TextLayoutCache {
pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
Self {
@@ -207,20 +169,10 @@ struct StyleRun {
underline: UnderlineStyle,
}
-#[derive(Default, Debug)]
-pub struct LineLayout {
- pub font_size: Pixels,
- pub width: Pixels,
- pub ascent: Pixels,
- pub descent: Pixels,
- pub runs: Vec<Run>,
- pub len: usize,
-}
-
-#[derive(Debug)]
-pub struct Run {
- pub font_id: FontId,
- pub glyphs: Vec<Glyph>,
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ShapedBoundary {
+ pub run_ix: usize,
+ pub glyph_ix: usize,
}
impl Line {
@@ -311,7 +263,7 @@ impl Line {
for run in &self.layout.runs {
let max_glyph_width = cx
- .font_cache()
+ .text_system()
.bounding_box(run.font_id, self.layout.font_size)
.width;
@@ -485,7 +437,7 @@ impl Line {
let _glyph_bounds = Bounds {
origin: glyph_origin,
size: cx
- .font_cache()
+ .text_system()
.bounding_box(run.font_id, self.layout.font_size),
};
// todo!()
@@ -528,353 +480,3 @@ impl Run {
&self.glyphs
}
}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct Boundary {
- pub ix: usize,
- pub next_indent: u32,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ShapedBoundary {
- pub run_ix: usize,
- pub glyph_ix: usize,
-}
-
-impl Boundary {
- fn new(ix: usize, next_indent: u32) -> Self {
- Self { ix, next_indent }
- }
-}
-
-pub struct LineWrapper {
- font_system: Arc<dyn PlatformTextSystem>,
- pub(crate) font_id: FontId,
- pub(crate) font_size: Pixels,
- cached_ascii_char_widths: [Option<Pixels>; 128],
- cached_other_char_widths: HashMap<char, Pixels>,
-}
-
-impl LineWrapper {
- pub const MAX_INDENT: u32 = 256;
-
- pub fn new(
- font_id: FontId,
- font_size: Pixels,
- font_system: Arc<dyn PlatformTextSystem>,
- ) -> Self {
- Self {
- font_system,
- font_id,
- font_size,
- cached_ascii_char_widths: [None; 128],
- cached_other_char_widths: HashMap::new(),
- }
- }
-
- pub fn wrap_line<'a>(
- &'a mut self,
- line: &'a str,
- wrap_width: Pixels,
- ) -> impl Iterator<Item = Boundary> + 'a {
- let mut width = px(0.);
- let mut first_non_whitespace_ix = None;
- let mut indent = None;
- let mut last_candidate_ix = 0;
- let mut last_candidate_width = px(0.);
- let mut last_wrap_ix = 0;
- let mut prev_c = '\0';
- let mut char_indices = line.char_indices();
- iter::from_fn(move || {
- for (ix, c) in char_indices.by_ref() {
- if c == '\n' {
- continue;
- }
-
- if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
- last_candidate_ix = ix;
- last_candidate_width = width;
- }
-
- if c != ' ' && first_non_whitespace_ix.is_none() {
- first_non_whitespace_ix = Some(ix);
- }
-
- let char_width = self.width_for_char(c);
- width += char_width;
- if width > wrap_width && ix > last_wrap_ix {
- if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
- {
- indent = Some(
- Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
- );
- }
-
- if last_candidate_ix > 0 {
- last_wrap_ix = last_candidate_ix;
- width -= last_candidate_width;
- last_candidate_ix = 0;
- } else {
- last_wrap_ix = ix;
- width = char_width;
- }
-
- if let Some(indent) = indent {
- width += self.width_for_char(' ') * indent as f32;
- }
-
- return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
- }
- prev_c = c;
- }
-
- None
- })
- }
-
- pub fn wrap_shaped_line<'a>(
- &'a mut self,
- str: &'a str,
- line: &'a Line,
- wrap_width: Pixels,
- ) -> impl Iterator<Item = ShapedBoundary> + 'a {
- let mut first_non_whitespace_ix = None;
- let mut last_candidate_ix = None;
- let mut last_candidate_x = px(0.);
- let mut last_wrap_ix = ShapedBoundary {
- run_ix: 0,
- glyph_ix: 0,
- };
- let mut last_wrap_x = px(0.);
- let mut prev_c = '\0';
- let mut glyphs = line
- .runs()
- .iter()
- .enumerate()
- .flat_map(move |(run_ix, run)| {
- run.glyphs()
- .iter()
- .enumerate()
- .map(move |(glyph_ix, glyph)| {
- let character = str[glyph.index..].chars().next().unwrap();
- (
- ShapedBoundary { run_ix, glyph_ix },
- character,
- glyph.position.x,
- )
- })
- })
- .peekable();
-
- iter::from_fn(move || {
- while let Some((ix, c, x)) = glyphs.next() {
- if c == '\n' {
- continue;
- }
-
- if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
- last_candidate_ix = Some(ix);
- last_candidate_x = x;
- }
-
- if c != ' ' && first_non_whitespace_ix.is_none() {
- first_non_whitespace_ix = Some(ix);
- }
-
- let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
- let width = next_x - last_wrap_x;
- if width > wrap_width && ix > last_wrap_ix {
- if let Some(last_candidate_ix) = last_candidate_ix.take() {
- last_wrap_ix = last_candidate_ix;
- last_wrap_x = last_candidate_x;
- } else {
- last_wrap_ix = ix;
- last_wrap_x = x;
- }
-
- return Some(last_wrap_ix);
- }
- prev_c = c;
- }
-
- None
- })
- }
-
- fn is_boundary(&self, prev: char, next: char) -> bool {
- (prev == ' ') && (next != ' ')
- }
-
- #[inline(always)]
- fn width_for_char(&mut self, c: char) -> Pixels {
- if (c as u32) < 128 {
- if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
- cached_width
- } else {
- let width = self.compute_width_for_char(c);
- self.cached_ascii_char_widths[c as usize] = Some(width);
- width
- }
- } else {
- if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
- *cached_width
- } else {
- let width = self.compute_width_for_char(c);
- self.cached_other_char_widths.insert(c, width);
- width
- }
- }
- }
-
- fn compute_width_for_char(&self, c: char) -> Pixels {
- self.font_system
- .layout_line(
- &c.to_string(),
- self.font_size,
- &[(
- 1,
- RunStyle {
- font_id: self.font_id,
- color: Default::default(),
- underline: Default::default(),
- },
- )],
- )
- .width
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::{AppContext, FontWeight};
-
- #[test]
- fn test_wrap_line() {
- let cx = AppContext::test();
-
- let font_cache = cx.font_cache().clone();
- let font_system = cx.platform().text_system();
- let family = font_cache
- .load_family(&["Courier"], &Default::default())
- .unwrap();
- let font_id = font_cache
- .select_font(family, Default::default(), Default::default())
- .unwrap();
-
- let mut wrapper = LineWrapper::new(font_id, px(16.), font_system);
- assert_eq!(
- wrapper
- .wrap_line("aa bbb cccc ddddd eeee", px(72.))
- .collect::<Vec<_>>(),
- &[
- Boundary::new(7, 0),
- Boundary::new(12, 0),
- Boundary::new(18, 0)
- ],
- );
- assert_eq!(
- wrapper
- .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
- .collect::<Vec<_>>(),
- &[
- Boundary::new(4, 0),
- Boundary::new(11, 0),
- Boundary::new(18, 0)
- ],
- );
- assert_eq!(
- wrapper
- .wrap_line(" aaaaaaa", px(72.))
- .collect::<Vec<_>>(),
- &[
- Boundary::new(7, 5),
- Boundary::new(9, 5),
- Boundary::new(11, 5),
- ]
- );
- assert_eq!(
- wrapper
- .wrap_line(" ", px(72.))
- .collect::<Vec<_>>(),
- &[
- Boundary::new(7, 0),
- Boundary::new(14, 0),
- Boundary::new(21, 0)
- ]
- );
- assert_eq!(
- wrapper
- .wrap_line(" aaaaaaaaaaaaaa", px(72.))
- .collect::<Vec<_>>(),
- &[
- Boundary::new(7, 0),
- Boundary::new(14, 3),
- Boundary::new(18, 3),
- Boundary::new(22, 3),
- ]
- );
- }
-
- // todo! repeat this test
- #[test]
- fn test_wrap_shaped_line() {
- let cx = AppContext::test();
- let font_cache = cx.font_cache().clone();
- let font_system = cx.platform().text_system();
- let text_layout_cache = TextLayoutCache::new(font_system.clone());
-
- let family = font_cache
- .load_family(&["Helvetica"], &Default::default())
- .unwrap();
- let font_id = font_cache
- .select_font(family, Default::default(), Default::default())
- .unwrap();
- let normal = RunStyle {
- font_id,
- color: Default::default(),
- underline: Default::default(),
- };
- let bold = RunStyle {
- font_id: font_cache
- .select_font(family, FontWeight::BOLD, Default::default())
- .unwrap(),
- color: Default::default(),
- underline: Default::default(),
- };
-
- let text = "aa bbb cccc ddddd eeee";
- let line = text_layout_cache.layout_str(
- text,
- px(16.),
- &[
- (4, normal.clone()),
- (5, bold.clone()),
- (6, normal.clone()),
- (1, bold),
- (7, normal),
- ],
- );
-
- let mut wrapper = LineWrapper::new(font_id, px(16.), font_system);
- assert_eq!(
- wrapper
- .wrap_shaped_line(text, &line, px(72.))
- .collect::<Vec<_>>(),
- &[
- ShapedBoundary {
- run_ix: 1,
- glyph_ix: 3
- },
- ShapedBoundary {
- run_ix: 2,
- glyph_ix: 3
- },
- ShapedBoundary {
- run_ix: 4,
- glyph_ix: 2
- }
- ],
- );
- }
-}
@@ -1,7 +1,7 @@
-use crate::{PlatformWindow, Point, TextStyleRefinement};
+use crate::{PlatformWindow, Point, Style, TextStyleRefinement};
use super::{
- px, taffy::LayoutId, AppContext, Bounds, Context, EntityId, Handle, Pixels, Reference, Style,
+ px, taffy::LayoutId, AppContext, Bounds, Context, EntityId, Handle, Pixels, Reference,
TaffyLayoutEngine,
};
use anyhow::Result;