Separate WrappedLines from ShapedLines

ShapedLines are never wrapped, whereas WrappedLines are optionally wrapped if
they are associated with a wrap width. I tried to combine everything because
wrapping is inherently optional for the Text element, but we have a bunch of
APIs that don't make sense on a line that may wrap, so we need a distinct type
for that case.
This commit is contained in:
Nathan Sobo
2023-11-16 23:10:51 -07:00
parent e5ada92b7b
commit 9558da8681
12 changed files with 563 additions and 330 deletions
+152 -107
View File
@@ -1,5 +1,5 @@
use crate::{
black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
black, point, px, BorrowWindow, Bounds, Hsla, LineLayout, Pixels, Point, Result, SharedString,
UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
};
use derive_more::{Deref, DerefMut};
@@ -14,23 +14,17 @@ pub struct DecorationRun {
}
#[derive(Clone, Default, Debug, Deref, DerefMut)]
pub struct Line {
pub struct ShapedLine {
#[deref]
#[deref_mut]
pub(crate) layout: Arc<WrappedLineLayout>,
pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
pub(crate) layout: Arc<LineLayout>,
pub text: SharedString,
pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
}
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 wrap_count(&self) -> usize {
self.layout.wrap_boundaries.len()
impl ShapedLine {
pub fn len(&self) -> usize {
self.layout.len
}
pub fn paint(
@@ -39,75 +33,84 @@ impl Line {
line_height: Pixels,
cx: &mut WindowContext,
) -> Result<()> {
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);
paint_line(
origin,
&self.layout,
line_height,
&self.decoration_runs,
None,
&[],
cx,
)?;
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();
Ok(())
}
}
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;
#[derive(Clone, Default, Debug, Deref, DerefMut)]
pub struct WrappedLine {
#[deref]
#[deref_mut]
pub(crate) layout: Arc<WrappedLineLayout>,
pub text: SharedString,
pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
}
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
impl WrappedLine {
pub fn len(&self) -> usize {
self.layout.len()
}
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,
)?;
}
pub fn paint(
&self,
origin: Point<Pixels>,
line_height: Pixels,
cx: &mut WindowContext,
) -> Result<()> {
paint_line(
origin,
&self.layout.unwrapped_layout,
line_height,
&self.decoration_runs,
self.wrap_width,
&self.wrap_boundaries,
cx,
)?;
glyph_origin.x = origin.x;
glyph_origin.y += line_height;
}
prev_glyph_position = glyph.position;
Ok(())
}
}
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
if glyph.index >= run_end {
if let Some(style_run) = style_runs.next() {
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(run_underline) = style_run.underline.as_ref() {
current_underline.get_or_insert((
point(
glyph_origin.x,
origin.y
+ baseline_offset.y
+ (self.layout.layout.descent * 0.618),
),
UnderlineStyle {
color: Some(run_underline.color.unwrap_or(style_run.color)),
thickness: run_underline.thickness,
wavy: run_underline.wavy,
},
));
}
fn paint_line(
origin: Point<Pixels>,
layout: &LineLayout,
line_height: Pixels,
decoration_runs: &[DecorationRun],
wrap_width: Option<Pixels>,
wrap_boundaries: &[WrapBoundary],
cx: &mut WindowContext<'_>,
) -> Result<()> {
let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
let baseline_offset = point(px(0.), padding_top + layout.ascent);
let mut decoration_runs = decoration_runs.iter();
let mut wraps = 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();
let mut glyph_origin = origin;
let mut prev_glyph_position = Point::default();
for (run_ix, run) in layout.runs.iter().enumerate() {
let max_glyph_size = text_system
.bounding_box(run.font_id, layout.font_size)?
.size;
run_end += style_run.len as usize;
color = style_run.color;
} else {
run_end = self.layout.text.len();
finished_underline = current_underline.take();
}
}
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
if let Some((underline_origin, underline_style)) = finished_underline {
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,
@@ -115,42 +118,84 @@ impl Line {
)?;
}
let max_glyph_bounds = Bounds {
origin: glyph_origin,
size: max_glyph_size,
};
glyph_origin.x = origin.x;
glyph_origin.y += line_height;
}
prev_glyph_position = glyph.position;
let content_mask = cx.content_mask();
if max_glyph_bounds.intersects(&content_mask.bounds) {
if glyph.is_emoji {
cx.paint_emoji(
glyph_origin + baseline_offset,
run.font_id,
glyph.id,
self.layout.layout.font_size,
)?;
} else {
cx.paint_glyph(
glyph_origin + baseline_offset,
run.font_id,
glyph.id,
self.layout.layout.font_size,
color,
)?;
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
if glyph.index >= run_end {
if let Some(style_run) = decoration_runs.next() {
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(run_underline) = style_run.underline.as_ref() {
current_underline.get_or_insert((
point(
glyph_origin.x,
origin.y + baseline_offset.y + (layout.descent * 0.618),
),
UnderlineStyle {
color: Some(run_underline.color.unwrap_or(style_run.color)),
thickness: run_underline.thickness,
wavy: run_underline.wavy,
},
));
}
run_end += style_run.len as usize;
color = style_run.color;
} else {
run_end = layout.len;
finished_underline = current_underline.take();
}
}
if let Some((underline_origin, underline_style)) = finished_underline {
cx.paint_underline(
underline_origin,
glyph_origin.x - underline_origin.x,
&underline_style,
)?;
}
let max_glyph_bounds = Bounds {
origin: glyph_origin,
size: max_glyph_size,
};
let content_mask = cx.content_mask();
if max_glyph_bounds.intersects(&content_mask.bounds) {
if glyph.is_emoji {
cx.paint_emoji(
glyph_origin + baseline_offset,
run.font_id,
glyph.id,
layout.font_size,
)?;
} else {
cx.paint_glyph(
glyph_origin + baseline_offset,
run.font_id,
glyph.id,
layout.font_size,
color,
)?;
}
}
}
if let Some((underline_start, underline_style)) = current_underline.take() {
let line_end_x = origin.x + self.layout.layout.width;
cx.paint_underline(
underline_start,
line_end_x - underline_start.x,
&underline_style,
)?;
}
Ok(())
}
if let Some((underline_start, underline_style)) = current_underline.take() {
let line_end_x = origin.x + wrap_width.unwrap_or(Pixels::MAX).min(layout.width);
cx.paint_underline(
underline_start,
line_end_x - underline_start.x,
&underline_style,
)?;
}
Ok(())
}
+104 -32
View File
@@ -1,5 +1,4 @@
use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, SharedString};
use derive_more::{Deref, DerefMut};
use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, Size};
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec;
use std::{
@@ -149,13 +148,11 @@ impl LineLayout {
}
}
#[derive(Deref, DerefMut, Default, Debug)]
#[derive(Default, Debug)]
pub struct WrappedLineLayout {
#[deref]
#[deref_mut]
pub layout: LineLayout,
pub text: SharedString,
pub unwrapped_layout: Arc<LineLayout>,
pub wrap_boundaries: SmallVec<[WrapBoundary; 1]>,
pub wrap_width: Option<Pixels>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
@@ -164,31 +161,74 @@ pub struct WrapBoundary {
pub glyph_ix: usize,
}
impl WrappedLineLayout {
pub fn len(&self) -> usize {
self.unwrapped_layout.len
}
pub fn width(&self) -> Pixels {
self.wrap_width
.unwrap_or(Pixels::MAX)
.min(self.unwrapped_layout.width)
}
pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
Size {
width: self.width(),
height: line_height * (self.wrap_boundaries.len() + 1),
}
}
pub fn ascent(&self) -> Pixels {
self.unwrapped_layout.ascent
}
pub fn descent(&self) -> Pixels {
self.unwrapped_layout.descent
}
pub fn wrap_boundaries(&self) -> &[WrapBoundary] {
&self.wrap_boundaries
}
pub fn font_size(&self) -> Pixels {
self.unwrapped_layout.font_size
}
pub fn runs(&self) -> &[ShapedRun] {
&self.unwrapped_layout.runs
}
}
pub(crate) struct LineLayoutCache {
prev_frame: Mutex<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
curr_frame: RwLock<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
previous_frame: Mutex<HashMap<CacheKey, Arc<LineLayout>>>,
current_frame: RwLock<HashMap<CacheKey, Arc<LineLayout>>>,
previous_frame_wrapped: Mutex<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
current_frame_wrapped: 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()),
previous_frame: Mutex::default(),
current_frame: RwLock::default(),
previous_frame_wrapped: Mutex::default(),
current_frame_wrapped: RwLock::default(),
platform_text_system,
}
}
pub fn start_frame(&self) {
let mut prev_frame = self.prev_frame.lock();
let mut curr_frame = self.curr_frame.write();
let mut prev_frame = self.previous_frame.lock();
let mut curr_frame = self.current_frame.write();
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
curr_frame.clear();
}
pub fn layout_line(
pub fn layout_wrapped_line(
&self,
text: &SharedString,
text: &str,
font_size: Pixels,
runs: &[FontRun],
wrap_width: Option<Pixels>,
@@ -199,34 +239,66 @@ impl LineLayoutCache {
runs,
wrap_width,
} as &dyn AsCacheKeyRef;
let curr_frame = self.curr_frame.upgradable_read();
if let Some(layout) = curr_frame.get(key) {
let current_frame = self.current_frame_wrapped.upgradable_read();
if let Some(layout) = current_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());
let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
if let Some((key, layout)) = self.previous_frame_wrapped.lock().remove_entry(key) {
current_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(),
let unwrapped_layout = self.layout_line(text, font_size, runs);
let wrap_boundaries = if let Some(wrap_width) = wrap_width {
unwrapped_layout.compute_wrap_boundaries(text.as_ref(), wrap_width)
} else {
SmallVec::new()
};
let layout = Arc::new(WrappedLineLayout {
unwrapped_layout,
wrap_boundaries,
wrap_width,
});
let key = CacheKey {
text: text.clone(),
text: text.into(),
font_size,
runs: SmallVec::from(runs),
wrap_width,
};
curr_frame.insert(key, wrapped_line.clone());
wrapped_line
current_frame.insert(key, layout.clone());
layout
}
}
pub fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> Arc<LineLayout> {
let key = &CacheKeyRef {
text,
font_size,
runs,
wrap_width: None,
} as &dyn AsCacheKeyRef;
let current_frame = self.current_frame.upgradable_read();
if let Some(layout) = current_frame.get(key) {
return layout.clone();
}
let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
if let Some((key, layout)) = self.previous_frame.lock().remove_entry(key) {
current_frame.insert(key, layout.clone());
layout
} else {
let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
let key = CacheKey {
text: text.into(),
font_size,
runs: SmallVec::from(runs),
wrap_width: None,
};
current_frame.insert(key, layout.clone());
layout
}
}
}
@@ -243,7 +315,7 @@ trait AsCacheKeyRef {
#[derive(Eq)]
struct CacheKey {
text: SharedString,
text: String,
font_size: Pixels,
runs: SmallVec<[FontRun; 1]>,
wrap_width: Option<Pixels>,