Checkpoint
This commit is contained in:
parent
2472142532
commit
695a24d8a7
9 changed files with 458 additions and 571 deletions
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
size, AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, SharedString, Size,
|
AnyElement, BorrowWindow, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels,
|
||||||
ViewContext,
|
SharedString, Size, ViewContext,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -90,7 +90,7 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let size = Size {
|
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(),
|
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 line_height = element_state.line_height;
|
||||||
let mut line_origin = bounds.origin;
|
let mut line_origin = bounds.origin;
|
||||||
for line in &element_state.lines {
|
for line in &element_state.lines {
|
||||||
let line_bounds = Bounds {
|
line.paint(line_origin, line_height, cx).log_err();
|
||||||
origin: line_origin,
|
line_origin.y += line.size(line_height).height;
|
||||||
size: size(line.width(), line_height),
|
|
||||||
};
|
|
||||||
line.paint(line_bounds, line_bounds, line_height, cx)
|
|
||||||
.log_err();
|
|
||||||
line_origin.y += line_height;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextElementState {
|
pub struct TextElementState {
|
||||||
lines: SmallVec<[Arc<Line>; 1]>,
|
lines: SmallVec<[Line; 1]>,
|
||||||
line_height: Pixels,
|
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_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(
|
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout;
|
||||||
&self,
|
|
||||||
text: &SharedString,
|
|
||||||
font_size: Pixels,
|
|
||||||
runs: &[(usize, FontId)],
|
|
||||||
) -> LineLayout;
|
|
||||||
fn wrap_line(
|
fn wrap_line(
|
||||||
&self,
|
&self,
|
||||||
text: &str,
|
text: &str,
|
||||||
|
|
|
@ -151,7 +151,7 @@ impl PlatformTextSystem for MacTextSystem {
|
||||||
|
|
||||||
fn layout_line(
|
fn layout_line(
|
||||||
&self,
|
&self,
|
||||||
text: &SharedString,
|
text: &str,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
font_runs: &[(usize, FontId)],
|
font_runs: &[(usize, FontId)],
|
||||||
) -> LineLayout {
|
) -> LineLayout {
|
||||||
|
@ -339,7 +339,7 @@ impl MacTextSystemState {
|
||||||
|
|
||||||
fn layout_line(
|
fn layout_line(
|
||||||
&mut self,
|
&mut self,
|
||||||
text: &SharedString,
|
text: &str,
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
font_runs: &[(usize, FontId)],
|
font_runs: &[(usize, FontId)],
|
||||||
) -> LineLayout {
|
) -> LineLayout {
|
||||||
|
@ -416,7 +416,6 @@ impl MacTextSystemState {
|
||||||
|
|
||||||
let typographic_bounds = line.get_typographic_bounds();
|
let typographic_bounds = line.get_typographic_bounds();
|
||||||
LineLayout {
|
LineLayout {
|
||||||
text: text.clone(),
|
|
||||||
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(),
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
mod font_features;
|
mod font_features;
|
||||||
mod line;
|
mod line;
|
||||||
|
mod line_layout;
|
||||||
mod line_wrapper;
|
mod line_wrapper;
|
||||||
mod text_layout_cache;
|
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
pub use font_features::*;
|
pub use font_features::*;
|
||||||
pub use line::*;
|
pub use line::*;
|
||||||
|
pub use line_layout::*;
|
||||||
use line_wrapper::*;
|
use line_wrapper::*;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
pub use text_layout_cache::*;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
|
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 const SUBPIXEL_VARIANTS: u8 = 4;
|
||||||
|
|
||||||
pub struct TextSystem {
|
pub struct TextSystem {
|
||||||
text_layout_cache: Arc<TextLayoutCache>,
|
line_layout_cache: Arc<LineLayoutCache>,
|
||||||
platform_text_system: Arc<dyn PlatformTextSystem>,
|
platform_text_system: Arc<dyn PlatformTextSystem>,
|
||||||
font_ids_by_font: RwLock<HashMap<Font, FontId>>,
|
font_ids_by_font: RwLock<HashMap<Font, FontId>>,
|
||||||
font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
|
font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
|
||||||
|
@ -46,7 +46,7 @@ pub struct TextSystem {
|
||||||
impl TextSystem {
|
impl TextSystem {
|
||||||
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
|
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
|
||||||
TextSystem {
|
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,
|
platform_text_system,
|
||||||
font_metrics: RwLock::new(HashMap::default()),
|
font_metrics: RwLock::new(HashMap::default()),
|
||||||
font_ids_by_font: RwLock::new(HashMap::default()),
|
font_ids_by_font: RwLock::new(HashMap::default()),
|
||||||
|
@ -151,7 +151,7 @@ impl TextSystem {
|
||||||
font_size: Pixels,
|
font_size: Pixels,
|
||||||
runs: &[TextRun],
|
runs: &[TextRun],
|
||||||
wrap_width: Option<Pixels>,
|
wrap_width: Option<Pixels>,
|
||||||
) -> Result<SmallVec<[Arc<Line>; 1]>> {
|
) -> Result<SmallVec<[Line; 1]>> {
|
||||||
let mut runs = runs.iter().cloned().peekable();
|
let mut runs = runs.iter().cloned().peekable();
|
||||||
let mut font_runs: Vec<(usize, FontId)> =
|
let mut font_runs: Vec<(usize, FontId)> =
|
||||||
self.font_runs_pool.lock().pop().unwrap_or_default();
|
self.font_runs_pool.lock().pop().unwrap_or_default();
|
||||||
|
@ -204,9 +204,12 @@ impl TextSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
let layout = self
|
let layout = self
|
||||||
.text_layout_cache
|
.line_layout_cache
|
||||||
.layout_line(&line_text, font_size, &font_runs);
|
.layout_line(&line_text, font_size, &font_runs, wrap_width);
|
||||||
lines.push(Arc::new(Line::new(layout, decoration_runs)));
|
lines.push(Line {
|
||||||
|
layout,
|
||||||
|
decorations: decoration_runs,
|
||||||
|
});
|
||||||
|
|
||||||
line_start = line_end + 1; // Skip `\n` character.
|
line_start = line_end + 1; // Skip `\n` character.
|
||||||
font_runs.clear();
|
font_runs.clear();
|
||||||
|
@ -218,7 +221,7 @@ impl TextSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end_frame(&self) {
|
pub fn end_frame(&self) {
|
||||||
self.text_layout_cache.end_frame()
|
self.line_layout_cache.end_frame()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line_wrapper(
|
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)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct RenderGlyphParams {
|
pub struct RenderGlyphParams {
|
||||||
pub(crate) font_id: FontId,
|
pub(crate) font_id: FontId,
|
||||||
|
|
|
@ -1,17 +1,10 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
black, point, px, Bounds, FontId, Hsla, LineLayout, Pixels, Point, ShapedBoundary, ShapedRun,
|
black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
|
||||||
UnderlineStyle, WindowContext,
|
UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
|
||||||
pub struct Line {
|
|
||||||
layout: Arc<LineLayout>,
|
|
||||||
decoration_runs: SmallVec<[DecorationRun; 32]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DecorationRun {
|
pub struct DecorationRun {
|
||||||
pub len: u32,
|
pub len: u32,
|
||||||
|
@ -19,101 +12,63 @@ pub struct DecorationRun {
|
||||||
pub underline: Option<UnderlineStyle>,
|
pub underline: Option<UnderlineStyle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug)]
|
||||||
|
pub struct Line {
|
||||||
|
pub(crate) layout: Arc<WrappedLineLayout>,
|
||||||
|
pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Line {
|
impl Line {
|
||||||
pub fn new(layout: Arc<LineLayout>, decoration_runs: SmallVec<[DecorationRun; 32]>) -> Self {
|
pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
|
||||||
Self {
|
size(
|
||||||
layout,
|
self.layout.width,
|
||||||
decoration_runs,
|
line_height * (self.layout.wrap_boundaries.len() + 1),
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint(
|
pub fn paint(
|
||||||
&self,
|
&self,
|
||||||
bounds: Bounds<Pixels>,
|
origin: Point<Pixels>,
|
||||||
visible_bounds: Bounds<Pixels>, // todo!("use clipping")
|
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let origin = bounds.origin;
|
let padding_top =
|
||||||
let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
|
(line_height - self.layout.layout.ascent - self.layout.layout.descent) / 2.;
|
||||||
let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
|
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 run_end = 0;
|
||||||
let mut color = black();
|
let mut color = black();
|
||||||
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||||
let text_system = cx.text_system().clone();
|
let text_system = cx.text_system().clone();
|
||||||
|
|
||||||
for run in &self.layout.runs {
|
let mut glyph_origin = origin;
|
||||||
let max_glyph_width = text_system
|
let mut prev_glyph_position = Point::default();
|
||||||
.bounding_box(run.font_id, self.layout.font_size)?
|
for (run_ix, run) in self.layout.layout.runs.iter().enumerate() {
|
||||||
.size
|
let max_glyph_size = text_system
|
||||||
.width;
|
.bounding_box(run.font_id, self.layout.layout.font_size)?
|
||||||
|
.size;
|
||||||
|
|
||||||
for glyph in &run.glyphs {
|
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
|
||||||
let glyph_origin = origin + baseline_offset + glyph.position;
|
glyph_origin.x += glyph.position.x - prev_glyph_position.x;
|
||||||
if glyph_origin.x > visible_bounds.upper_right().x {
|
|
||||||
break;
|
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,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
|
||||||
if glyph.index >= run_end {
|
if glyph.index >= run_end {
|
||||||
if let Some(style_run) = style_runs.next() {
|
if let Some(style_run) = style_runs.next() {
|
||||||
|
@ -126,7 +81,9 @@ impl Line {
|
||||||
current_underline.get_or_insert((
|
current_underline.get_or_insert((
|
||||||
point(
|
point(
|
||||||
glyph_origin.x,
|
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 {
|
UnderlineStyle {
|
||||||
color: Some(run_underline.color.unwrap_or(style_run.color)),
|
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 {
|
if let Some((underline_origin, underline_style)) = finished_underline {
|
||||||
cx.paint_underline(
|
cx.paint_underline(
|
||||||
underline_origin,
|
underline_origin,
|
||||||
|
@ -156,22 +109,35 @@ impl Line {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
if glyph.is_emoji {
|
||||||
cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?;
|
cx.paint_emoji(
|
||||||
|
glyph_origin,
|
||||||
|
run.font_id,
|
||||||
|
glyph.id,
|
||||||
|
self.layout.layout.font_size,
|
||||||
|
)?;
|
||||||
} else {
|
} else {
|
||||||
cx.paint_glyph(
|
cx.paint_glyph(
|
||||||
glyph_origin,
|
glyph_origin,
|
||||||
run.font_id,
|
run.font_id,
|
||||||
glyph.id,
|
glyph.id,
|
||||||
self.layout.font_size,
|
self.layout.layout.font_size,
|
||||||
color,
|
color,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((underline_start, underline_style)) = current_underline.take() {
|
if let Some((underline_start, underline_style)) = current_underline.take() {
|
||||||
let line_end_x = origin.x + self.layout.width;
|
let line_end_x = origin.x + self.layout.layout.width;
|
||||||
cx.paint_underline(
|
cx.paint_underline(
|
||||||
underline_start,
|
underline_start,
|
||||||
line_end_x - underline_start.x,
|
line_end_x - underline_start.x,
|
||||||
|
@ -181,123 +147,4 @@ impl Line {
|
||||||
|
|
||||||
Ok(())
|
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;
|
|
||||||
|
|
||||||
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,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
cx.paint_underline(
|
|
||||||
underline_origin,
|
|
||||||
line_end_x - underline_origin.x,
|
|
||||||
&underline_style,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
295
crates/gpui3/src/text_system/line_layout.rs
Normal file
295
crates/gpui3/src/text_system/line_layout.rs
Normal file
|
@ -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 collections::HashMap;
|
||||||
use std::{iter, sync::Arc};
|
use std::{iter, sync::Arc};
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ impl LineWrapper {
|
||||||
continue;
|
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_ix = ix;
|
||||||
last_candidate_width = width;
|
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)]
|
#[inline(always)]
|
||||||
fn width_for_char(&mut self, c: char) -> Pixels {
|
fn width_for_char(&mut self, c: char) -> Pixels {
|
||||||
if (c as u32) < 128 {
|
if (c as u32) < 128 {
|
||||||
|
@ -182,8 +109,10 @@ impl LineWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_width_for_char(&self, c: char) -> Pixels {
|
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
|
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
|
.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,7 +132,7 @@ impl Boundary {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{font, App, TextRun};
|
use crate::{font, App};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wrap_line() {
|
fn test_wrap_line() {
|
||||||
|
@ -268,74 +197,75 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo!("move this to a test on TextSystem::layout_text")
|
||||||
// todo! repeat this test
|
// todo! repeat this test
|
||||||
#[test]
|
// #[test]
|
||||||
fn test_wrap_shaped_line() {
|
// fn test_wrap_shaped_line() {
|
||||||
App::test().run(|cx| {
|
// App::test().run(|cx| {
|
||||||
let text_system = cx.text_system().clone();
|
// let text_system = cx.text_system().clone();
|
||||||
|
|
||||||
let normal = TextRun {
|
// let normal = TextRun {
|
||||||
len: 0,
|
// len: 0,
|
||||||
font: font("Helvetica"),
|
// font: font("Helvetica"),
|
||||||
color: Default::default(),
|
// color: Default::default(),
|
||||||
underline: Default::default(),
|
// underline: Default::default(),
|
||||||
};
|
// };
|
||||||
let bold = TextRun {
|
// let bold = TextRun {
|
||||||
len: 0,
|
// len: 0,
|
||||||
font: font("Helvetica").bold(),
|
// font: font("Helvetica").bold(),
|
||||||
color: Default::default(),
|
// color: Default::default(),
|
||||||
underline: Default::default(),
|
// underline: Default::default(),
|
||||||
};
|
// };
|
||||||
|
|
||||||
impl TextRun {
|
// impl TextRun {
|
||||||
fn with_len(&self, len: usize) -> Self {
|
// fn with_len(&self, len: usize) -> Self {
|
||||||
let mut this = self.clone();
|
// let mut this = self.clone();
|
||||||
this.len = len;
|
// this.len = len;
|
||||||
this
|
// this
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
let text = "aa bbb cccc ddddd eeee".into();
|
// let text = "aa bbb cccc ddddd eeee".into();
|
||||||
let lines = text_system
|
// let lines = text_system
|
||||||
.layout_text(
|
// .layout_text(
|
||||||
&text,
|
// &text,
|
||||||
px(16.),
|
// px(16.),
|
||||||
&[
|
// &[
|
||||||
normal.with_len(4),
|
// normal.with_len(4),
|
||||||
bold.with_len(5),
|
// bold.with_len(5),
|
||||||
normal.with_len(6),
|
// normal.with_len(6),
|
||||||
bold.with_len(1),
|
// bold.with_len(1),
|
||||||
normal.with_len(7),
|
// normal.with_len(7),
|
||||||
],
|
// ],
|
||||||
None,
|
// None,
|
||||||
)
|
// )
|
||||||
.unwrap();
|
// .unwrap();
|
||||||
let line = &lines[0];
|
// let line = &lines[0];
|
||||||
|
|
||||||
let mut wrapper = LineWrapper::new(
|
// let mut wrapper = LineWrapper::new(
|
||||||
text_system.font_id(&normal.font).unwrap(),
|
// text_system.font_id(&normal.font).unwrap(),
|
||||||
px(16.),
|
// px(16.),
|
||||||
text_system.platform_text_system.clone(),
|
// text_system.platform_text_system.clone(),
|
||||||
);
|
// );
|
||||||
assert_eq!(
|
// assert_eq!(
|
||||||
wrapper
|
// wrapper
|
||||||
.wrap_shaped_line(&text, &line, px(72.))
|
// .wrap_shaped_line(&text, &line, px(72.))
|
||||||
.collect::<Vec<_>>(),
|
// .collect::<Vec<_>>(),
|
||||||
&[
|
// &[
|
||||||
ShapedBoundary {
|
// ShapedBoundary {
|
||||||
run_ix: 1,
|
// run_ix: 1,
|
||||||
glyph_ix: 3
|
// glyph_ix: 3
|
||||||
},
|
// },
|
||||||
ShapedBoundary {
|
// ShapedBoundary {
|
||||||
run_ix: 2,
|
// run_ix: 2,
|
||||||
glyph_ix: 3
|
// glyph_ix: 3
|
||||||
},
|
// },
|
||||||
ShapedBoundary {
|
// ShapedBoundary {
|
||||||
run_ix: 4,
|
// run_ix: 4,
|
||||||
glyph_ix: 2
|
// 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 {
|
pub struct Window {
|
||||||
handle: AnyWindowHandle,
|
handle: AnyWindowHandle,
|
||||||
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
|
platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
|
||||||
pub(crate) display_id: DisplayId, // todo!("make private again?")
|
display_id: DisplayId,
|
||||||
sprite_atlas: Arc<dyn PlatformAtlas>,
|
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||||
rem_size: Pixels,
|
rem_size: Pixels,
|
||||||
content_size: Size<Pixels>,
|
content_size: Size<Pixels>,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue