Detailed changes
@@ -1,6 +1,6 @@
use crate::{
- size, AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, SharedString, Size,
- ViewContext,
+ AnyElement, BorrowWindow, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels,
+ SharedString, Size, ViewContext,
};
use parking_lot::Mutex;
use smallvec::SmallVec;
@@ -90,7 +90,7 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
};
let size = Size {
- width: lines.iter().map(|line| line.width()).max().unwrap(),
+ width: lines.iter().map(|line| line.layout.width).max().unwrap(),
height: line_height * lines.len(),
};
@@ -119,18 +119,13 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
let line_height = element_state.line_height;
let mut line_origin = bounds.origin;
for line in &element_state.lines {
- let line_bounds = Bounds {
- origin: line_origin,
- size: size(line.width(), line_height),
- };
- line.paint(line_bounds, line_bounds, line_height, cx)
- .log_err();
- line_origin.y += line_height;
+ line.paint(line_origin, line_height, cx).log_err();
+ line_origin.y += line.size(line_height).height;
}
}
}
pub struct TextElementState {
- lines: SmallVec<[Arc<Line>; 1]>,
+ lines: SmallVec<[Line; 1]>,
line_height: Pixels,
}
@@ -171,12 +171,7 @@ pub trait PlatformTextSystem: Send + Sync {
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>;
- fn layout_line(
- &self,
- text: &SharedString,
- font_size: Pixels,
- runs: &[(usize, FontId)],
- ) -> LineLayout;
+ fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout;
fn wrap_line(
&self,
text: &str,
@@ -151,7 +151,7 @@ impl PlatformTextSystem for MacTextSystem {
fn layout_line(
&self,
- text: &SharedString,
+ text: &str,
font_size: Pixels,
font_runs: &[(usize, FontId)],
) -> LineLayout {
@@ -339,7 +339,7 @@ impl MacTextSystemState {
fn layout_line(
&mut self,
- text: &SharedString,
+ text: &str,
font_size: Pixels,
font_runs: &[(usize, FontId)],
) -> LineLayout {
@@ -416,7 +416,6 @@ impl MacTextSystemState {
let typographic_bounds = line.get_typographic_bounds();
LineLayout {
- text: text.clone(),
width: typographic_bounds.width.into(),
ascent: typographic_bounds.ascent.into(),
descent: typographic_bounds.descent.into(),
@@ -1,14 +1,14 @@
mod font_features;
mod line;
+mod line_layout;
mod line_wrapper;
-mod text_layout_cache;
use anyhow::anyhow;
pub use font_features::*;
pub use line::*;
+pub use line_layout::*;
use line_wrapper::*;
use smallvec::SmallVec;
-pub use text_layout_cache::*;
use crate::{
px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
@@ -35,7 +35,7 @@ pub struct FontFamilyId(pub usize);
pub const SUBPIXEL_VARIANTS: u8 = 4;
pub struct TextSystem {
- text_layout_cache: Arc<TextLayoutCache>,
+ line_layout_cache: Arc<LineLayoutCache>,
platform_text_system: Arc<dyn PlatformTextSystem>,
font_ids_by_font: RwLock<HashMap<Font, FontId>>,
font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
@@ -46,7 +46,7 @@ pub struct TextSystem {
impl TextSystem {
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
TextSystem {
- text_layout_cache: Arc::new(TextLayoutCache::new(platform_text_system.clone())),
+ line_layout_cache: Arc::new(LineLayoutCache::new(platform_text_system.clone())),
platform_text_system,
font_metrics: RwLock::new(HashMap::default()),
font_ids_by_font: RwLock::new(HashMap::default()),
@@ -151,7 +151,7 @@ impl TextSystem {
font_size: Pixels,
runs: &[TextRun],
wrap_width: Option<Pixels>,
- ) -> Result<SmallVec<[Arc<Line>; 1]>> {
+ ) -> Result<SmallVec<[Line; 1]>> {
let mut runs = runs.iter().cloned().peekable();
let mut font_runs: Vec<(usize, FontId)> =
self.font_runs_pool.lock().pop().unwrap_or_default();
@@ -204,9 +204,12 @@ impl TextSystem {
}
let layout = self
- .text_layout_cache
- .layout_line(&line_text, font_size, &font_runs);
- lines.push(Arc::new(Line::new(layout, decoration_runs)));
+ .line_layout_cache
+ .layout_line(&line_text, font_size, &font_runs, wrap_width);
+ lines.push(Line {
+ layout,
+ decorations: decoration_runs,
+ });
line_start = line_end + 1; // Skip `\n` character.
font_runs.clear();
@@ -218,7 +221,7 @@ impl TextSystem {
}
pub fn end_frame(&self) {
- self.text_layout_cache.end_frame()
+ self.line_layout_cache.end_frame()
}
pub fn line_wrapper(
@@ -390,30 +393,6 @@ impl From<u32> for GlyphId {
}
}
-#[derive(Default, Debug)]
-pub struct LineLayout {
- pub text: SharedString,
- pub font_size: Pixels,
- pub width: Pixels,
- pub ascent: Pixels,
- pub descent: Pixels,
- pub runs: Vec<ShapedRun>,
-}
-
-#[derive(Debug)]
-pub struct ShapedRun {
- pub font_id: FontId,
- pub glyphs: SmallVec<[ShapedGlyph; 8]>,
-}
-
-#[derive(Clone, Debug)]
-pub struct ShapedGlyph {
- pub id: GlyphId,
- pub position: Point<Pixels>,
- pub index: usize,
- pub is_emoji: bool,
-}
-
#[derive(Clone, Debug, PartialEq)]
pub struct RenderGlyphParams {
pub(crate) font_id: FontId,
@@ -1,17 +1,10 @@
use crate::{
- black, point, px, Bounds, FontId, Hsla, LineLayout, Pixels, Point, ShapedBoundary, ShapedRun,
- UnderlineStyle, WindowContext,
+ black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
+ UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
};
-use anyhow::Result;
use smallvec::SmallVec;
use std::sync::Arc;
-#[derive(Default, Debug, Clone)]
-pub struct Line {
- layout: Arc<LineLayout>,
- decoration_runs: SmallVec<[DecorationRun; 32]>,
-}
-
#[derive(Debug, Clone)]
pub struct DecorationRun {
pub len: u32,
@@ -19,100 +12,62 @@ pub struct DecorationRun {
pub underline: Option<UnderlineStyle>,
}
-impl Line {
- pub fn new(layout: Arc<LineLayout>, decoration_runs: SmallVec<[DecorationRun; 32]>) -> Self {
- Self {
- layout,
- decoration_runs,
- }
- }
-
- pub fn runs(&self) -> &[ShapedRun] {
- &self.layout.runs
- }
-
- pub fn width(&self) -> Pixels {
- self.layout.width
- }
-
- pub fn font_size(&self) -> Pixels {
- self.layout.font_size
- }
-
- pub fn x_for_index(&self, index: usize) -> Pixels {
- for run in &self.layout.runs {
- for glyph in &run.glyphs {
- if glyph.index >= index {
- return glyph.position.x;
- }
- }
- }
- self.layout.width
- }
-
- pub fn font_for_index(&self, index: usize) -> Option<FontId> {
- for run in &self.layout.runs {
- for glyph in &run.glyphs {
- if glyph.index >= index {
- return Some(run.font_id);
- }
- }
- }
-
- None
- }
-
- pub fn len(&self) -> usize {
- self.layout.text.len()
- }
-
- pub fn is_empty(&self) -> bool {
- self.layout.text.is_empty()
- }
+#[derive(Clone, Default, Debug)]
+pub struct Line {
+ pub(crate) layout: Arc<WrappedLineLayout>,
+ pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
+}
- pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
- if x >= self.layout.width {
- None
- } else {
- for run in self.layout.runs.iter().rev() {
- for glyph in run.glyphs.iter().rev() {
- if glyph.position.x <= x {
- return Some(glyph.index);
- }
- }
- }
- Some(0)
- }
+impl Line {
+ pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
+ size(
+ self.layout.width,
+ line_height * (self.layout.wrap_boundaries.len() + 1),
+ )
}
pub fn paint(
&self,
- bounds: Bounds<Pixels>,
- visible_bounds: Bounds<Pixels>, // todo!("use clipping")
+ origin: Point<Pixels>,
line_height: Pixels,
cx: &mut WindowContext,
) -> Result<()> {
- let origin = bounds.origin;
- let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
- let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
+ let padding_top =
+ (line_height - self.layout.layout.ascent - self.layout.layout.descent) / 2.;
+ let baseline_offset = point(px(0.), padding_top + self.layout.layout.ascent);
- let mut style_runs = self.decoration_runs.iter();
+ let mut style_runs = self.decorations.iter();
+ let mut wraps = self.layout.wrap_boundaries.iter().peekable();
let mut run_end = 0;
let mut color = black();
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let text_system = cx.text_system().clone();
- for run in &self.layout.runs {
- let max_glyph_width = text_system
- .bounding_box(run.font_id, self.layout.font_size)?
- .size
- .width;
+ let mut glyph_origin = origin;
+ let mut prev_glyph_position = Point::default();
+ for (run_ix, run) in self.layout.layout.runs.iter().enumerate() {
+ let max_glyph_size = text_system
+ .bounding_box(run.font_id, self.layout.layout.font_size)?
+ .size;
+
+ for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
+ glyph_origin.x += glyph.position.x - prev_glyph_position.x;
+
+ if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
+ wraps.next();
+ if let Some((underline_origin, underline_style)) = current_underline.take() {
+ cx.paint_underline(
+ underline_origin,
+ glyph_origin.x - underline_origin.x,
+ &underline_style,
+ )?;
+ }
- for glyph in &run.glyphs {
- let glyph_origin = origin + baseline_offset + glyph.position;
- if glyph_origin.x > visible_bounds.upper_right().x {
- break;
+ glyph_origin.x = origin.x;
+ glyph_origin.y += line_height;
}
+ prev_glyph_position = glyph.position;
+ let glyph_origin = glyph_origin + baseline_offset;
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
if glyph.index >= run_end {
@@ -126,7 +81,9 @@ impl Line {
current_underline.get_or_insert((
point(
glyph_origin.x,
- origin.y + baseline_offset.y + (self.layout.descent * 0.618),
+ origin.y
+ + baseline_offset.y
+ + (self.layout.layout.descent * 0.618),
),
UnderlineStyle {
color: Some(run_underline.color.unwrap_or(style_run.color)),
@@ -144,10 +101,6 @@ impl Line {
}
}
- if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
- continue;
- }
-
if let Some((underline_origin, underline_style)) = finished_underline {
cx.paint_underline(
underline_origin,
@@ -156,144 +109,38 @@ impl Line {
)?;
}
- if glyph.is_emoji {
- cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?;
- } else {
- cx.paint_glyph(
- glyph_origin,
- run.font_id,
- glyph.id,
- self.layout.font_size,
- color,
- )?;
- }
- }
- }
-
- if let Some((underline_start, underline_style)) = current_underline.take() {
- let line_end_x = origin.x + self.layout.width;
- cx.paint_underline(
- underline_start,
- line_end_x - underline_start.x,
- &underline_style,
- )?;
- }
-
- Ok(())
- }
-
- pub fn paint_wrapped(
- &self,
- origin: Point<Pixels>,
- _visible_bounds: Bounds<Pixels>, // todo!("use clipping")
- line_height: Pixels,
- boundaries: &[ShapedBoundary],
- cx: &mut WindowContext,
- ) -> Result<()> {
- let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
- let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
-
- let mut boundaries = boundaries.into_iter().peekable();
- let mut color_runs = self.decoration_runs.iter();
- let mut style_run_end = 0;
- let mut _color = black(); // todo!
- let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
-
- let mut glyph_origin = origin;
- let mut prev_position = px(0.);
- for (run_ix, run) in self.layout.runs.iter().enumerate() {
- for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
- glyph_origin.x += glyph.position.x - prev_position;
+ let max_glyph_bounds = Bounds {
+ origin: glyph_origin,
+ size: max_glyph_size,
+ };
- if boundaries
- .peek()
- .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
- {
- boundaries.next();
- if let Some((underline_origin, underline_style)) = current_underline.take() {
- cx.paint_underline(
- underline_origin,
- glyph_origin.x - underline_origin.x,
- &underline_style,
+ let content_mask = cx.content_mask();
+ if max_glyph_bounds.intersects(&content_mask.bounds) {
+ if glyph.is_emoji {
+ cx.paint_emoji(
+ glyph_origin,
+ run.font_id,
+ glyph.id,
+ self.layout.layout.font_size,
)?;
- }
-
- glyph_origin = point(origin.x, glyph_origin.y + line_height);
- }
- prev_position = glyph.position.x;
-
- let mut finished_underline = None;
- if glyph.index >= style_run_end {
- if let Some(style_run) = color_runs.next() {
- style_run_end += style_run.len as usize;
- _color = style_run.color;
- if let Some((_, underline_style)) = &mut current_underline {
- if style_run.underline.as_ref() != Some(underline_style) {
- finished_underline = current_underline.take();
- }
- }
- if let Some(underline_style) = style_run.underline.as_ref() {
- current_underline.get_or_insert((
- glyph_origin
- + point(
- px(0.),
- baseline_offset.y + (self.layout.descent * 0.618),
- ),
- UnderlineStyle {
- color: Some(underline_style.color.unwrap_or(style_run.color)),
- thickness: underline_style.thickness,
- wavy: underline_style.wavy,
- },
- ));
- }
} else {
- style_run_end = self.layout.text.len();
- _color = black();
- finished_underline = current_underline.take();
+ cx.paint_glyph(
+ glyph_origin,
+ run.font_id,
+ glyph.id,
+ self.layout.layout.font_size,
+ color,
+ )?;
}
}
-
- if let Some((underline_origin, underline_style)) = finished_underline {
- cx.paint_underline(
- underline_origin,
- glyph_origin.x - underline_origin.x,
- &underline_style,
- )?;
- }
-
- let text_system = cx.text_system();
- let _glyph_bounds = Bounds {
- origin: glyph_origin,
- size: text_system
- .bounding_box(run.font_id, self.layout.font_size)?
- .size,
- };
- // if glyph_bounds.intersects(visible_bounds) {
- // if glyph.is_emoji {
- // cx.scene().push_image_glyph(scene::ImageGlyph {
- // font_id: run.font_id,
- // font_size: self.layout.font_size,
- // id: glyph.id,
- // origin: glyph_bounds.origin() + baseline_offset,
- // });
- // } else {
- // cx.scene().push_glyph(scene::Glyph {
- // font_id: run.font_id,
- // font_size: self.layout.font_size,
- // id: glyph.id,
- // origin: glyph_bounds.origin() + baseline_offset,
- // color,
- // });
- // }
- // }
}
}
- if let Some((underline_origin, underline_style)) = current_underline.take() {
- let line_end_x = glyph_origin.x + self.layout.width - prev_position;
+ if let Some((underline_start, underline_style)) = current_underline.take() {
+ let line_end_x = origin.x + self.layout.layout.width;
cx.paint_underline(
- underline_origin,
- line_end_x - underline_origin.x,
+ underline_start,
+ line_end_x - underline_start.x,
&underline_style,
)?;
}
@@ -0,0 +1,295 @@
+use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, SharedString};
+use derive_more::{Deref, DerefMut};
+use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
+use smallvec::SmallVec;
+use std::{
+ borrow::Borrow,
+ collections::HashMap,
+ hash::{Hash, Hasher},
+ sync::Arc,
+};
+
+#[derive(Default, Debug)]
+pub struct LineLayout {
+ pub font_size: Pixels,
+ pub width: Pixels,
+ pub ascent: Pixels,
+ pub descent: Pixels,
+ pub runs: Vec<ShapedRun>,
+}
+
+#[derive(Debug)]
+pub struct ShapedRun {
+ pub font_id: FontId,
+ pub glyphs: SmallVec<[ShapedGlyph; 8]>,
+}
+
+#[derive(Clone, Debug)]
+pub struct ShapedGlyph {
+ pub id: GlyphId,
+ pub position: Point<Pixels>,
+ pub index: usize,
+ pub is_emoji: bool,
+}
+
+impl LineLayout {
+ pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
+ if x >= self.width {
+ None
+ } else {
+ for run in self.runs.iter().rev() {
+ for glyph in run.glyphs.iter().rev() {
+ if glyph.position.x <= x {
+ return Some(glyph.index);
+ }
+ }
+ }
+ Some(0)
+ }
+ }
+
+ pub fn x_for_index(&self, index: usize) -> Pixels {
+ for run in &self.runs {
+ for glyph in &run.glyphs {
+ if glyph.index >= index {
+ return glyph.position.x;
+ }
+ }
+ }
+ self.width
+ }
+
+ pub fn font_for_index(&self, index: usize) -> Option<FontId> {
+ for run in &self.runs {
+ for glyph in &run.glyphs {
+ if glyph.index >= index {
+ return Some(run.font_id);
+ }
+ }
+ }
+
+ None
+ }
+
+ fn compute_wrap_boundaries(
+ &self,
+ text: &str,
+ wrap_width: Pixels,
+ ) -> SmallVec<[WrapBoundary; 1]> {
+ let mut boundaries = SmallVec::new();
+
+ let mut first_non_whitespace_ix = None;
+ let mut last_candidate_ix = None;
+ let mut last_candidate_x = px(0.);
+ let mut last_boundary = WrapBoundary {
+ run_ix: 0,
+ glyph_ix: 0,
+ };
+ let mut last_boundary_x = px(0.);
+ let mut prev_ch = '\0';
+ let mut glyphs = self
+ .runs
+ .iter()
+ .enumerate()
+ .flat_map(move |(run_ix, run)| {
+ run.glyphs.iter().enumerate().map(move |(glyph_ix, glyph)| {
+ let character = text[glyph.index..].chars().next().unwrap();
+ (
+ WrapBoundary { run_ix, glyph_ix },
+ character,
+ glyph.position.x,
+ )
+ })
+ })
+ .peekable();
+
+ while let Some((boundary, ch, x)) = glyphs.next() {
+ if ch == '\n' {
+ continue;
+ }
+
+ if prev_ch == ' ' && ch != ' ' && first_non_whitespace_ix.is_some() {
+ last_candidate_ix = Some(boundary);
+ last_candidate_x = x;
+ }
+
+ if ch != ' ' && first_non_whitespace_ix.is_none() {
+ first_non_whitespace_ix = Some(boundary);
+ }
+
+ let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
+ let width = next_x - last_boundary_x;
+ if width > wrap_width && boundary > last_boundary {
+ if let Some(last_candidate_ix) = last_candidate_ix.take() {
+ last_boundary = last_candidate_ix;
+ last_boundary_x = last_candidate_x;
+ } else {
+ last_boundary = boundary;
+ last_boundary_x = x;
+ }
+
+ boundaries.push(last_boundary);
+ }
+ prev_ch = ch;
+ }
+
+ boundaries
+ }
+}
+
+#[derive(Deref, DerefMut, Default, Debug)]
+pub struct WrappedLineLayout {
+ #[deref]
+ #[deref_mut]
+ pub layout: LineLayout,
+ pub text: SharedString,
+ pub wrap_boundaries: SmallVec<[WrapBoundary; 1]>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct WrapBoundary {
+ pub run_ix: usize,
+ pub glyph_ix: usize,
+}
+
+pub(crate) struct LineLayoutCache {
+ prev_frame: Mutex<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
+ curr_frame: RwLock<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
+ platform_text_system: Arc<dyn PlatformTextSystem>,
+}
+
+impl LineLayoutCache {
+ pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
+ Self {
+ prev_frame: Mutex::new(HashMap::new()),
+ curr_frame: RwLock::new(HashMap::new()),
+ platform_text_system,
+ }
+ }
+
+ pub fn end_frame(&self) {
+ let mut prev_frame = self.prev_frame.lock();
+ let mut curr_frame = self.curr_frame.write();
+ std::mem::swap(&mut *prev_frame, &mut *curr_frame);
+ curr_frame.clear();
+ }
+
+ pub fn layout_line(
+ &self,
+ text: &SharedString,
+ font_size: Pixels,
+ runs: &[(usize, FontId)],
+ wrap_width: Option<Pixels>,
+ ) -> Arc<WrappedLineLayout> {
+ let key = &CacheKeyRef {
+ text,
+ font_size,
+ runs,
+ } as &dyn AsCacheKeyRef;
+ let curr_frame = self.curr_frame.upgradable_read();
+ if let Some(layout) = curr_frame.get(key) {
+ return layout.clone();
+ }
+
+ let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
+ if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
+ curr_frame.insert(key, layout.clone());
+ layout
+ } else {
+ let layout = self.platform_text_system.layout_line(text, font_size, runs);
+ let wrap_boundaries = wrap_width
+ .map(|wrap_width| layout.compute_wrap_boundaries(text.as_ref(), wrap_width))
+ .unwrap_or_default();
+ let wrapped_line = Arc::new(WrappedLineLayout {
+ layout,
+ text: text.clone(),
+ wrap_boundaries,
+ });
+
+ let key = CacheKey {
+ text: text.clone(),
+ font_size,
+ runs: SmallVec::from(runs),
+ };
+ curr_frame.insert(key, wrapped_line.clone());
+ wrapped_line
+ }
+ }
+}
+
+trait AsCacheKeyRef {
+ fn as_cache_key_ref(&self) -> CacheKeyRef;
+}
+
+#[derive(Eq)]
+struct CacheKey {
+ text: SharedString,
+ font_size: Pixels,
+ runs: SmallVec<[(usize, FontId); 1]>,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+struct CacheKeyRef<'a> {
+ text: &'a str,
+ font_size: Pixels,
+ runs: &'a [(usize, FontId)],
+}
+
+impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
+ fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
+ self.as_cache_key_ref() == other.as_cache_key_ref()
+ }
+}
+
+impl<'a> Eq for (dyn AsCacheKeyRef + 'a) {}
+
+impl<'a> Hash for (dyn AsCacheKeyRef + 'a) {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.as_cache_key_ref().hash(state)
+ }
+}
+
+impl AsCacheKeyRef for CacheKey {
+ fn as_cache_key_ref(&self) -> CacheKeyRef {
+ CacheKeyRef {
+ text: &self.text,
+ font_size: self.font_size,
+ runs: self.runs.as_slice(),
+ }
+ }
+}
+
+impl PartialEq for CacheKey {
+ fn eq(&self, other: &Self) -> bool {
+ self.as_cache_key_ref().eq(&other.as_cache_key_ref())
+ }
+}
+
+impl Hash for CacheKey {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.as_cache_key_ref().hash(state);
+ }
+}
+
+impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for CacheKey {
+ fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
+ self as &dyn AsCacheKeyRef
+ }
+}
+
+impl<'a> AsCacheKeyRef for CacheKeyRef<'a> {
+ fn as_cache_key_ref(&self) -> CacheKeyRef {
+ *self
+ }
+}
+
+impl<'a> Hash for CacheKeyRef<'a> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.text.hash(state);
+ self.font_size.hash(state);
+ for (len, font_id) in self.runs {
+ len.hash(state);
+ font_id.hash(state);
+ }
+ }
+}
@@ -1,4 +1,4 @@
-use crate::{px, FontId, Line, Pixels, PlatformTextSystem, ShapedBoundary, SharedString};
+use crate::{px, FontId, Pixels, PlatformTextSystem};
use collections::HashMap;
use std::{iter, sync::Arc};
@@ -46,7 +46,7 @@ impl LineWrapper {
continue;
}
- if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
+ if prev_c == ' ' && c != ' ' && first_non_whitespace_ix.is_some() {
last_candidate_ix = ix;
last_candidate_width = width;
}
@@ -87,79 +87,6 @@ impl LineWrapper {
})
}
- pub fn wrap_shaped_line<'a>(
- &'a mut self,
- str: &'a SharedString,
- 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 {
@@ -182,8 +109,10 @@ impl LineWrapper {
}
fn compute_width_for_char(&self, c: char) -> Pixels {
+ let mut buffer = [0; 4];
+ let buffer = c.encode_utf8(&mut buffer);
self.platform_text_system
- .layout_line(&c.to_string().into(), self.font_size, &[(1, self.font_id)])
+ .layout_line(buffer, self.font_size, &[(1, self.font_id)])
.width
}
}
@@ -203,7 +132,7 @@ impl Boundary {
#[cfg(test)]
mod tests {
use super::*;
- use crate::{font, App, TextRun};
+ use crate::{font, App};
#[test]
fn test_wrap_line() {
@@ -268,74 +197,75 @@ mod tests {
});
}
+ // todo!("move this to a test on TextSystem::layout_text")
// todo! repeat this test
- #[test]
- fn test_wrap_shaped_line() {
- App::test().run(|cx| {
- let text_system = cx.text_system().clone();
+ // #[test]
+ // fn test_wrap_shaped_line() {
+ // App::test().run(|cx| {
+ // let text_system = cx.text_system().clone();
- let normal = TextRun {
- len: 0,
- font: font("Helvetica"),
- color: Default::default(),
- underline: Default::default(),
- };
- let bold = TextRun {
- len: 0,
- font: font("Helvetica").bold(),
- color: Default::default(),
- underline: Default::default(),
- };
+ // let normal = TextRun {
+ // len: 0,
+ // font: font("Helvetica"),
+ // color: Default::default(),
+ // underline: Default::default(),
+ // };
+ // let bold = TextRun {
+ // len: 0,
+ // font: font("Helvetica").bold(),
+ // color: Default::default(),
+ // underline: Default::default(),
+ // };
- impl TextRun {
- fn with_len(&self, len: usize) -> Self {
- let mut this = self.clone();
- this.len = len;
- this
- }
- }
+ // impl TextRun {
+ // fn with_len(&self, len: usize) -> Self {
+ // let mut this = self.clone();
+ // this.len = len;
+ // this
+ // }
+ // }
- let text = "aa bbb cccc ddddd eeee".into();
- let lines = text_system
- .layout_text(
- &text,
- px(16.),
- &[
- normal.with_len(4),
- bold.with_len(5),
- normal.with_len(6),
- bold.with_len(1),
- normal.with_len(7),
- ],
- None,
- )
- .unwrap();
- let line = &lines[0];
+ // let text = "aa bbb cccc ddddd eeee".into();
+ // let lines = text_system
+ // .layout_text(
+ // &text,
+ // px(16.),
+ // &[
+ // normal.with_len(4),
+ // bold.with_len(5),
+ // normal.with_len(6),
+ // bold.with_len(1),
+ // normal.with_len(7),
+ // ],
+ // None,
+ // )
+ // .unwrap();
+ // let line = &lines[0];
- let mut wrapper = LineWrapper::new(
- text_system.font_id(&normal.font).unwrap(),
- 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
- }
- ],
- );
- });
- }
+ // let mut wrapper = LineWrapper::new(
+ // text_system.font_id(&normal.font).unwrap(),
+ // 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,153 +0,0 @@
-use crate::{FontId, LineLayout, Pixels, PlatformTextSystem, ShapedGlyph, ShapedRun, SharedString};
-use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
-use smallvec::SmallVec;
-use std::{
- borrow::Borrow,
- collections::HashMap,
- hash::{Hash, Hasher},
- sync::Arc,
-};
-
-pub(crate) struct TextLayoutCache {
- prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
- curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
- platform_text_system: Arc<dyn PlatformTextSystem>,
-}
-
-impl TextLayoutCache {
- pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
- Self {
- prev_frame: Mutex::new(HashMap::new()),
- curr_frame: RwLock::new(HashMap::new()),
- platform_text_system,
- }
- }
-
- pub fn end_frame(&self) {
- let mut prev_frame = self.prev_frame.lock();
- let mut curr_frame = self.curr_frame.write();
- std::mem::swap(&mut *prev_frame, &mut *curr_frame);
- curr_frame.clear();
- }
-
- pub fn layout_line(
- &self,
- text: &SharedString,
- font_size: Pixels,
- runs: &[(usize, FontId)],
- ) -> Arc<LineLayout> {
- let key = &CacheKeyRef {
- text,
- font_size,
- runs,
- } as &dyn CacheKey;
- let curr_frame = self.curr_frame.upgradable_read();
- if let Some(layout) = curr_frame.get(key) {
- return layout.clone();
- }
-
- let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
- if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
- curr_frame.insert(key, layout.clone());
- layout
- } else {
- let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
- let key = CacheKeyValue {
- text: text.clone(),
- font_size,
- runs: SmallVec::from(runs),
- };
- curr_frame.insert(key, layout.clone());
- layout
- }
- }
-}
-
-trait CacheKey {
- fn key(&self) -> CacheKeyRef;
-}
-
-impl<'a> PartialEq for (dyn CacheKey + 'a) {
- fn eq(&self, other: &dyn CacheKey) -> bool {
- self.key() == other.key()
- }
-}
-
-impl<'a> Eq for (dyn CacheKey + 'a) {}
-
-impl<'a> Hash for (dyn CacheKey + 'a) {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.key().hash(state)
- }
-}
-
-#[derive(Eq)]
-struct CacheKeyValue {
- text: SharedString,
- font_size: Pixels,
- runs: SmallVec<[(usize, FontId); 1]>,
-}
-
-impl CacheKey for CacheKeyValue {
- fn key(&self) -> CacheKeyRef {
- CacheKeyRef {
- text: &self.text,
- font_size: self.font_size,
- runs: self.runs.as_slice(),
- }
- }
-}
-
-impl PartialEq for CacheKeyValue {
- fn eq(&self, other: &Self) -> bool {
- self.key().eq(&other.key())
- }
-}
-
-impl Hash for CacheKeyValue {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.key().hash(state);
- }
-}
-
-impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
- fn borrow(&self) -> &(dyn CacheKey + 'a) {
- self as &dyn CacheKey
- }
-}
-
-#[derive(Copy, Clone, PartialEq, Eq)]
-struct CacheKeyRef<'a> {
- text: &'a str,
- font_size: Pixels,
- runs: &'a [(usize, FontId)],
-}
-
-impl<'a> CacheKey for CacheKeyRef<'a> {
- fn key(&self) -> CacheKeyRef {
- *self
- }
-}
-
-impl<'a> Hash for CacheKeyRef<'a> {
- fn hash<H: Hasher>(&self, state: &mut H) {
- self.text.hash(state);
- self.font_size.hash(state);
- for (len, font_id) in self.runs {
- len.hash(state);
- font_id.hash(state);
- }
- }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ShapedBoundary {
- pub run_ix: usize,
- pub glyph_ix: usize,
-}
-
-impl ShapedRun {
- pub fn glyphs(&self) -> &[ShapedGlyph] {
- &self.glyphs
- }
-}
@@ -45,7 +45,7 @@ type MouseEventHandler =
pub struct Window {
handle: AnyWindowHandle,
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
- pub(crate) display_id: DisplayId, // todo!("make private again?")
+ display_id: DisplayId,
sprite_atlas: Arc<dyn PlatformAtlas>,
rem_size: Pixels,
content_size: Size<Pixels>,