This commit is contained in:
Nathan Sobo 2023-10-13 14:38:13 -06:00
parent 1a3650ef2a
commit 847376cd8f
7 changed files with 66 additions and 28 deletions

View file

@ -74,12 +74,13 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
let rem_size = cx.rem_size(); let rem_size = cx.rem_size();
let layout_id = cx.request_measured_layout(Default::default(), rem_size, { let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
let element_state = element_state.clone(); let element_state = element_state.clone();
move |_, _| { move |known_dimensions, _| {
let Some(line_layout) = text_system let Some(line_layout) = text_system
.layout_line( .layout_text(
text.as_ref(), text.as_ref(),
font_size, font_size,
&[(text.len(), text_style.to_run())], &[(text.len(), text_style.to_run())],
known_dimensions.width, // Wrap if we know the width.
) )
.log_err() .log_err()
else { else {

View file

@ -6,8 +6,8 @@ mod test;
use crate::{ use crate::{
AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics,
GlobalPixels, GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, GlobalPixels, GlyphId, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams,
Result, Scene, ShapedLine, SharedString, Size, RenderSvgParams, Result, Scene, SharedString, Size,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use async_task::Runnable; use async_task::Runnable;
@ -171,7 +171,7 @@ pub trait PlatformTextSystem: Send + Sync {
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>; fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>; fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>; fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>;
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine; fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout;
fn wrap_line( fn wrap_line(
&self, &self,
text: &str, text: &str,

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle, point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle,
FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, ShapedGlyph, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result,
ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS, ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use cocoa::appkit::{CGFloat, CGPoint}; use cocoa::appkit::{CGFloat, CGPoint};
@ -154,7 +154,7 @@ impl PlatformTextSystem for MacTextSystem {
text: &str, text: &str,
font_size: Pixels, font_size: Pixels,
font_runs: &[(usize, FontId)], font_runs: &[(usize, FontId)],
) -> ShapedLine { ) -> LineLayout {
self.0.write().layout_line(text, font_size, font_runs) self.0.write().layout_line(text, font_size, font_runs)
} }
@ -342,7 +342,7 @@ impl MacTextSystemState {
text: &str, text: &str,
font_size: Pixels, font_size: Pixels,
font_runs: &[(usize, FontId)], font_runs: &[(usize, FontId)],
) -> ShapedLine { ) -> LineLayout {
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges. // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
let mut string = CFMutableAttributedString::new(); let mut string = CFMutableAttributedString::new();
{ {
@ -394,7 +394,7 @@ impl MacTextSystemState {
let font_id = self.id_for_native_font(font); let font_id = self.id_for_native_font(font);
let mut ix_converter = StringIndexConverter::new(text); let mut ix_converter = StringIndexConverter::new(text);
let mut glyphs = Vec::new(); let mut glyphs = SmallVec::new();
for ((glyph_id, position), glyph_utf16_ix) in run for ((glyph_id, position), glyph_utf16_ix) in run
.glyphs() .glyphs()
.iter() .iter()
@ -415,7 +415,7 @@ impl MacTextSystemState {
} }
let typographic_bounds = line.get_typographic_bounds(); let typographic_bounds = line.get_typographic_bounds();
ShapedLine { LineLayout {
width: typographic_bounds.width.into(), width: typographic_bounds.width.into(),
ascent: typographic_bounds.ascent.into(), ascent: typographic_bounds.ascent.into(),
descent: typographic_bounds.descent.into(), descent: typographic_bounds.descent.into(),

View file

@ -7,6 +7,7 @@ use anyhow::anyhow;
pub use font_features::*; pub use font_features::*;
pub use line::*; pub use line::*;
use line_wrapper::*; use line_wrapper::*;
use smallvec::SmallVec;
pub use text_layout_cache::*; pub use text_layout_cache::*;
use crate::{ use crate::{
@ -143,13 +144,15 @@ impl TextSystem {
} }
} }
pub fn layout_line( pub fn layout_text(
&self, &self,
text: &str, text: &str,
font_size: Pixels, font_size: Pixels,
runs: &[(usize, RunStyle)], runs: &[(usize, RunStyle)],
) -> Result<Line> { wrap_width: Option<Pixels>,
let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default(); ) -> Result<SmallVec<[Line; 1]>> {
let mut font_runs: Vec<(usize, FontId)> =
self.font_runs_pool.lock().pop().unwrap_or_default();
let mut last_font: Option<&Font> = None; let mut last_font: Option<&Font> = None;
for (len, style) in runs { for (len, style) in runs {
@ -163,14 +166,47 @@ impl TextSystem {
font_runs.push((*len, self.font_id(&style.font)?)); font_runs.push((*len, self.font_id(&style.font)?));
} }
let layout = self let mut layouts = SmallVec::new();
.text_layout_cache let mut start = 0;
.layout_line(text, font_size, &font_runs); let mut run_start = 0;
for line in text.lines() {
let end = start + line.len();
let mut run_end = run_start;
let mut line_length = 0;
for (len, _) in font_runs[run_start..].iter() {
line_length += len;
if *len >= line_length {
break;
}
run_end += 1;
}
// If a run lands in the middle of a line, create an additional run for the remaining characters.
if line_length < end - start {
// Create a run for the part that fits this line.
let partial_run = font_runs[run_end];
partial_run.0 = line_length;
layouts.push(self.text_layout_cache.layout_line(
&text[start..start + line_length],
font_size,
&font_runs[run_start..=run_end],
));
// Update the original run to only include the part that does not fit this line.
font_runs[run_end].0 -= line_length;
} else {
layouts.push(self.text_layout_cache.layout_line(
&text[start..end],
font_size,
&font_runs[run_start..run_end],
));
run_start = run_end;
}
start = end + 1;
}
font_runs.clear(); font_runs.clear();
self.font_runs_pool.lock().push(font_runs); self.font_runs_pool.lock().push(font_runs);
Ok(Line::new(layout.clone(), runs)) Ok(layouts)
} }
pub fn end_frame(&self) { pub fn end_frame(&self) {
@ -346,7 +382,7 @@ impl From<u32> for GlyphId {
} }
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct ShapedLine { pub struct LineLayout {
pub font_size: Pixels, pub font_size: Pixels,
pub width: Pixels, pub width: Pixels,
pub ascent: Pixels, pub ascent: Pixels,
@ -358,7 +394,7 @@ pub struct ShapedLine {
#[derive(Debug)] #[derive(Debug)]
pub struct ShapedRun { pub struct ShapedRun {
pub font_id: FontId, pub font_id: FontId,
pub glyphs: Vec<ShapedGlyph>, pub glyphs: SmallVec<[ShapedGlyph; 8]>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine, black, point, px, Bounds, FontId, Hsla, LineLayout, Pixels, Point, RunStyle, ShapedBoundary,
ShapedRun, UnderlineStyle, WindowContext, ShapedRun, UnderlineStyle, WindowContext,
}; };
use anyhow::Result; use anyhow::Result;
@ -8,7 +8,7 @@ use std::sync::Arc;
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct Line { pub struct Line {
layout: Arc<ShapedLine>, layout: Arc<LineLayout>,
style_runs: SmallVec<[StyleRun; 32]>, style_runs: SmallVec<[StyleRun; 32]>,
} }
@ -20,7 +20,7 @@ struct StyleRun {
} }
impl Line { impl Line {
pub fn new(layout: Arc<ShapedLine>, runs: &[(usize, RunStyle)]) -> Self { pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
let mut style_runs = SmallVec::new(); let mut style_runs = SmallVec::new();
for (len, style) in runs { for (len, style) in runs {
style_runs.push(StyleRun { style_runs.push(StyleRun {

View file

@ -287,7 +287,7 @@ mod tests {
let text = "aa bbb cccc ddddd eeee"; let text = "aa bbb cccc ddddd eeee";
let line = text_system let line = text_system
.layout_line( .layout_text(
text, text,
px(16.), px(16.),
&[ &[
@ -297,6 +297,7 @@ mod tests {
(1, bold.clone()), (1, bold.clone()),
(7, normal.clone()), (7, normal.clone()),
], ],
None,
) )
.unwrap(); .unwrap();

View file

@ -1,4 +1,4 @@
use crate::{FontId, Pixels, PlatformTextSystem, ShapedGlyph, ShapedLine, ShapedRun}; use crate::{FontId, LineLayout, Pixels, PlatformTextSystem, ShapedGlyph, ShapedRun};
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
@ -9,8 +9,8 @@ use std::{
}; };
pub(crate) struct TextLayoutCache { pub(crate) struct TextLayoutCache {
prev_frame: Mutex<HashMap<CacheKeyValue, Arc<ShapedLine>>>, prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
curr_frame: RwLock<HashMap<CacheKeyValue, Arc<ShapedLine>>>, curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
platform_text_system: Arc<dyn PlatformTextSystem>, platform_text_system: Arc<dyn PlatformTextSystem>,
} }
@ -35,7 +35,7 @@ impl TextLayoutCache {
text: &'a str, text: &'a str,
font_size: Pixels, font_size: Pixels,
runs: &[(usize, FontId)], runs: &[(usize, FontId)],
) -> Arc<ShapedLine> { ) -> Arc<LineLayout> {
let key = &CacheKeyRef { let key = &CacheKeyRef {
text, text,
font_size, font_size,