Checkpoint

This commit is contained in:
Nathan Sobo 2023-09-20 12:03:37 -06:00
parent 83dae46ec6
commit 5b0e333967
16 changed files with 794 additions and 582 deletions

View file

@ -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") },

View file

@ -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"

View file

@ -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>(

View file

@ -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(

View file

@ -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::*;

View file

@ -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>;

View file

@ -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);
}
});

View file

@ -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::*;

View file

@ -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 {

View file

@ -1,4 +1,4 @@
use crate::{text::GlyphId, FontId};
use crate::{FontId, GlyphId};
use super::{Bounds, Hsla, Pixels, Point};
use bytemuck::{Pod, Zeroable};

View file

@ -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;
}

View file

@ -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>,
}

View file

@ -1,16 +1,9 @@
use crate::{px, Bounds, LineWrapper, Pixels, PlatformTextSystem, Result, Size};
use crate::{
px, Bounds, FontFeatures, FontStyle, FontWeight, 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 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>,
}
pub(crate) struct FontCache(RwLock<FontCacheState>);
#[allow(non_camel_case_types)]
#[derive(Deserialize)]
enum WeightJson {
thin,
extra_light,
light,
normal,
medium,
semibold,
bold,
extra_bold,
black,
}
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::*;

View file

@ -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
}
],
);
}
}

View file

@ -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
}
],
);
}
}

View file

@ -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;