cosmic-text/src/buffer.rs

1408 lines
49 KiB
Rust
Raw Normal View History

2022-10-24 08:56:48 -06:00
// SPDX-License-Identifier: MIT OR Apache-2.0
2022-11-08 08:43:27 -07:00
#[cfg(not(feature = "std"))]
2023-11-15 09:21:13 -07:00
use alloc::{string::String, vec::Vec};
2023-01-04 20:03:03 -07:00
use core::{cmp, fmt};
2022-10-24 14:18:40 -06:00
use unicode_segmentation::UnicodeSegmentation;
2022-10-18 12:07:22 -06:00
use crate::{
Affinity, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color, Cursor,
FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding, LineIter, Motion, Scroll,
ShapeBuffer, ShapeLine, Shaping, Wrap,
};
2022-10-18 12:07:22 -06:00
2022-10-27 10:29:19 -06:00
/// A line of visible text for rendering
#[derive(Debug)]
2022-10-31 11:24:36 -06:00
pub struct LayoutRun<'a> {
2022-10-25 12:52:46 -06:00
/// The index of the original text line
pub line_i: usize,
2022-10-25 12:52:46 -06:00
/// The original text line
pub text: &'a str,
2022-10-25 13:15:52 -06:00
/// True if the original paragraph direction is RTL
2022-10-25 12:52:46 -06:00
pub rtl: bool,
/// The array of layout glyphs to draw
pub glyphs: &'a [LayoutGlyph],
/// Y offset to baseline of line
pub line_y: f32,
2023-06-16 07:03:43 -06:00
/// Y offset to top of line
pub line_top: f32,
2024-06-06 15:21:44 -06:00
/// Y offset to next line
pub line_height: f32,
/// Width of line
2022-12-15 04:59:31 +01:00
pub line_w: f32,
2022-10-25 12:52:46 -06:00
}
impl<'a> LayoutRun<'a> {
2023-02-28 19:42:53 +01:00
/// Return the pixel span `Some((x_left, x_width))` of the highlighted area between `cursor_start`
/// and `cursor_end` within this run, or None if the cursor range does not intersect this run.
/// This may return widths of zero if `cursor_start == cursor_end`, if the run is empty, or if the
/// region's left start boundary is the same as the cursor's end boundary or vice versa.
pub fn highlight(&self, cursor_start: Cursor, cursor_end: Cursor) -> Option<(f32, f32)> {
let mut x_start = None;
let mut x_end = None;
let rtl_factor = if self.rtl { 1. } else { 0. };
let ltr_factor = 1. - rtl_factor;
for glyph in self.glyphs.iter() {
2023-01-10 20:12:12 -07:00
let cursor = self.cursor_from_glyph_left(glyph);
if cursor >= cursor_start && cursor <= cursor_end {
if x_start.is_none() {
x_start = Some(glyph.x + glyph.w * rtl_factor);
}
x_end = Some(glyph.x + glyph.w * rtl_factor);
}
2023-01-10 20:12:12 -07:00
let cursor = self.cursor_from_glyph_right(glyph);
if cursor >= cursor_start && cursor <= cursor_end {
if x_start.is_none() {
x_start = Some(glyph.x + glyph.w * ltr_factor);
}
x_end = Some(glyph.x + glyph.w * ltr_factor);
}
}
if let Some(x_start) = x_start {
2023-01-04 20:02:00 -07:00
let x_end = x_end.expect("end of cursor not found");
2023-01-04 20:03:03 -07:00
let (x_start, x_end) = if x_start < x_end {
(x_start, x_end)
} else {
(x_end, x_start)
};
Some((x_start, x_end - x_start))
} else {
None
}
}
2023-01-10 20:12:12 -07:00
fn cursor_from_glyph_left(&self, glyph: &LayoutGlyph) -> Cursor {
if self.rtl {
Cursor::new_with_affinity(self.line_i, glyph.end, Affinity::Before)
2023-01-10 20:12:12 -07:00
} else {
Cursor::new_with_affinity(self.line_i, glyph.start, Affinity::After)
2023-01-10 20:12:12 -07:00
}
}
fn cursor_from_glyph_right(&self, glyph: &LayoutGlyph) -> Cursor {
if self.rtl {
Cursor::new_with_affinity(self.line_i, glyph.start, Affinity::After)
2023-01-10 20:12:12 -07:00
} else {
Cursor::new_with_affinity(self.line_i, glyph.end, Affinity::Before)
2023-01-10 20:12:12 -07:00
}
}
}
/// An iterator of visible text lines, see [`LayoutRun`]
#[derive(Debug)]
pub struct LayoutRunIter<'b> {
buffer: &'b Buffer,
2022-10-25 12:52:46 -06:00
line_i: usize,
layout_i: usize,
total_height: f32,
2024-06-06 15:21:44 -06:00
line_top: f32,
2022-10-25 12:52:46 -06:00
}
impl<'b> LayoutRunIter<'b> {
pub fn new(buffer: &'b Buffer) -> Self {
2022-10-25 12:52:46 -06:00
Self {
buffer,
line_i: buffer.scroll.line,
2022-10-25 12:52:46 -06:00
layout_i: 0,
total_height: 0.0,
2024-06-06 15:21:44 -06:00
line_top: 0.0,
2022-10-25 12:52:46 -06:00
}
}
}
impl<'b> Iterator for LayoutRunIter<'b> {
2022-10-31 11:24:36 -06:00
type Item = LayoutRun<'b>;
2022-10-25 12:52:46 -06:00
fn next(&mut self) -> Option<Self::Item> {
while let Some(line) = self.buffer.lines.get(self.line_i) {
let shape = line.shape_opt().as_ref()?;
let layout = line.layout_opt().as_ref()?;
2022-10-25 12:52:46 -06:00
while let Some(layout_line) = layout.get(self.layout_i) {
self.layout_i += 1;
let line_height = layout_line
.line_height_opt
.unwrap_or(self.buffer.metrics.line_height);
self.total_height += line_height;
let line_top = self.line_top - self.buffer.scroll.vertical;
let glyph_height = layout_line.max_ascent + layout_line.max_descent;
2024-06-06 15:21:44 -06:00
let centering_offset = (line_height - glyph_height) / 2.0;
2023-06-16 07:03:43 -06:00
let line_y = line_top + centering_offset + layout_line.max_ascent;
if line_top + centering_offset > self.buffer.height {
2022-10-25 12:52:46 -06:00
return None;
}
2024-06-06 15:21:44 -06:00
self.line_top += line_height;
if line_y < 0.0 {
continue;
}
2024-06-06 15:21:44 -06:00
return Some(LayoutRun {
line_i: self.line_i,
text: line.text(),
rtl: shape.rtl,
glyphs: &layout_line.glyphs,
line_y,
line_top,
line_height,
line_w: layout_line.w,
2022-10-25 12:52:46 -06:00
});
}
self.line_i += 1;
self.layout_i = 0;
}
None
}
2022-10-25 11:40:10 -06:00
}
2022-10-18 17:13:48 -06:00
/// Metrics of text
#[derive(Clone, Copy, Debug, Default, PartialEq)]
2022-10-31 11:24:36 -06:00
pub struct Metrics {
2022-10-18 17:13:48 -06:00
/// Font size in pixels
pub font_size: f32,
2022-10-18 17:13:48 -06:00
/// Line height in pixels
pub line_height: f32,
2022-10-18 17:04:22 -06:00
}
2022-10-31 11:24:36 -06:00
impl Metrics {
2024-06-06 14:40:35 -06:00
/// Create metrics with given font size and line height
pub const fn new(font_size: f32, line_height: f32) -> Self {
2023-01-04 20:03:03 -07:00
Self {
font_size,
line_height,
}
2022-10-18 17:04:22 -06:00
}
2024-06-06 14:40:35 -06:00
/// Create metrics with given font size and calculate line height using relative scale
pub fn relative(font_size: f32, line_height_scale: f32) -> Self {
Self {
font_size,
line_height: font_size * line_height_scale,
}
}
/// Scale font size and line height
pub fn scale(self, scale: f32) -> Self {
2022-10-18 17:04:22 -06:00
Self {
font_size: self.font_size * scale,
line_height: self.line_height * scale,
}
}
}
2022-10-31 11:24:36 -06:00
impl fmt::Display for Metrics {
2022-10-18 17:04:22 -06:00
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}px / {}px", self.font_size, self.line_height)
}
}
2022-10-18 17:13:48 -06:00
/// A buffer of text that is shaped and laid out
#[derive(Debug)]
pub struct Buffer {
/// [BufferLine]s (or paragraphs) of text in the buffer
2022-11-04 09:44:54 -06:00
pub lines: Vec<BufferLine>,
2022-10-31 11:24:36 -06:00
metrics: Metrics,
width: f32,
height: f32,
scroll: Scroll,
/// True if a redraw is requires. Set to false after processing
redraw: bool,
wrap: Wrap,
monospace_width: Option<f32>,
2024-06-06 21:01:46 -06:00
tab_width: u16,
/// Scratch buffer for shaping and laying out.
scratch: ShapeBuffer,
2022-10-18 12:07:22 -06:00
}
impl Clone for Buffer {
fn clone(&self) -> Self {
Self {
lines: self.lines.clone(),
metrics: self.metrics,
width: self.width,
height: self.height,
scroll: self.scroll,
redraw: self.redraw,
wrap: self.wrap,
monospace_width: self.monospace_width,
2024-06-06 21:01:46 -06:00
tab_width: self.tab_width,
scratch: ShapeBuffer::default(),
}
}
}
impl Buffer {
2023-05-12 10:23:43 +08:00
/// Create an empty [`Buffer`] with the provided [`Metrics`].
/// This is useful for initializing a [`Buffer`] without a [`FontSystem`].
///
/// You must populate the [`Buffer`] with at least one [`BufferLine`] before shaping and layout,
/// for example by calling [`Buffer::set_text`].
///
/// If you have a [`FontSystem`] in scope, you should use [`Buffer::new`] instead.
///
/// # Panics
///
/// Will panic if `metrics.line_height` is zero.
pub fn new_empty(metrics: Metrics) -> Self {
assert_ne!(metrics.line_height, 0.0, "line height cannot be 0");
Self {
lines: Vec::new(),
metrics,
width: 0.0,
height: 0.0,
scroll: Scroll::default(),
2023-05-12 10:23:43 +08:00
redraw: false,
2024-02-02 10:45:24 -07:00
wrap: Wrap::WordOrGlyph,
scratch: ShapeBuffer::default(),
monospace_width: None,
2024-06-06 21:01:46 -06:00
tab_width: 8,
2023-05-12 10:23:43 +08:00
}
}
/// Create a new [`Buffer`] with the provided [`FontSystem`] and [`Metrics`]
2023-02-28 19:42:53 +01:00
///
/// # Panics
///
/// Will panic if `metrics.line_height` is zero.
2023-03-12 10:30:20 +01:00
pub fn new(font_system: &mut FontSystem, metrics: Metrics) -> Self {
let mut buffer = Self::new_empty(metrics);
buffer.set_text(font_system, "", Attrs::new(), Shaping::Advanced);
buffer
2022-10-18 12:07:22 -06:00
}
2023-03-12 10:30:27 +01:00
/// Mutably borrows the buffer together with an [`FontSystem`] for more convenient methods
pub fn borrow_with<'a>(
&'a mut self,
font_system: &'a mut FontSystem,
) -> BorrowedWithFontSystem<'a, Buffer> {
BorrowedWithFontSystem {
inner: self,
font_system,
}
}
2023-03-12 10:30:27 +01:00
fn relayout(&mut self, font_system: &mut FontSystem) {
2023-01-23 10:57:06 -07:00
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
2022-11-08 08:43:27 -07:00
let instant = std::time::Instant::now();
2022-10-31 11:24:36 -06:00
for line in &mut self.lines {
2022-10-31 11:24:36 -06:00
if line.shape_opt().is_some() {
line.reset_layout();
line.layout_in_buffer(
&mut self.scratch,
font_system,
self.metrics.font_size,
Some(self.width),
self.wrap,
self.monospace_width,
2024-06-06 21:01:46 -06:00
self.tab_width,
);
2022-10-31 11:24:36 -06:00
}
}
self.redraw = true;
2023-01-23 10:57:06 -07:00
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
2022-11-08 08:43:27 -07:00
log::debug!("relayout: {:?}", instant.elapsed());
2022-10-31 11:24:36 -06:00
}
/// Shape lines until cursor, also scrolling to include cursor in view
pub fn shape_until_cursor(
&mut self,
font_system: &mut FontSystem,
cursor: Cursor,
prune: bool,
) {
let height = self.height;
let metrics = self.metrics;
let old_scroll = self.scroll;
let layout_cursor = self
.layout_cursor(font_system, cursor)
.expect("shape_until_cursor invalid cursor");
let mut layout_y = 0.0;
let mut total_height = {
let layout = self
.line_layout(font_system, layout_cursor.line)
.expect("shape_until_cursor failed to scroll forwards");
for layout_i in 0..layout_cursor.layout {
layout_y += layout[layout_i]
.line_height_opt
.unwrap_or(metrics.line_height);
}
layout_y
+ layout[layout_cursor.layout]
.line_height_opt
.unwrap_or(metrics.line_height)
};
if self.scroll.line > layout_cursor.line
|| (self.scroll.line == layout_cursor.line && self.scroll.vertical > layout_y)
{
// Adjust scroll backwards if cursor is before it
self.scroll.line = layout_cursor.line;
self.scroll.vertical = layout_y;
} else {
// Adjust scroll forwards if cursor is after it
let mut line_i = layout_cursor.line;
while line_i > self.scroll.line {
line_i -= 1;
let layout = self
.line_layout(font_system, line_i)
.expect("shape_until_cursor failed to scroll forwards");
for layout_line in layout.iter() {
total_height += layout_line.line_height_opt.unwrap_or(metrics.line_height);
}
if total_height > height + self.scroll.vertical {
self.scroll.line = line_i;
self.scroll.vertical = total_height - height;
}
}
2022-10-18 12:07:22 -06:00
}
if old_scroll != self.scroll {
self.redraw = true;
2022-10-18 12:07:22 -06:00
}
self.shape_until_scroll(font_system, prune);
// Adjust horizontal scroll to include cursor
if let Some(layout_cursor) = self.layout_cursor(font_system, cursor) {
if let Some(layout_lines) = self.line_layout(font_system, layout_cursor.line) {
if let Some(layout_line) = layout_lines.get(layout_cursor.layout) {
let (x_min, x_max) = if let Some(glyph) = layout_line
2024-05-30 14:33:35 -06:00
.glyphs
.get(layout_cursor.glyph)
.or_else(|| layout_line.glyphs.last())
{
//TODO: use code from cursor_glyph_opt?
let x_a = glyph.x;
let x_b = glyph.x + glyph.w;
(x_a.min(x_b), x_a.max(x_b))
} else {
(0.0, 0.0)
};
if x_min < self.scroll.horizontal {
self.scroll.horizontal = x_min;
self.redraw = true;
}
if x_max > self.scroll.horizontal + self.width {
self.scroll.horizontal = x_max - self.width;
self.redraw = true;
}
}
}
}
2022-10-18 12:07:22 -06:00
}
/// Shape lines until scroll
pub fn shape_until_scroll(&mut self, font_system: &mut FontSystem, prune: bool) {
let metrics = self.metrics;
let old_scroll = self.scroll;
loop {
// Adjust scroll.layout to be positive by moving scroll.line backwards
while self.scroll.vertical < 0.0 {
if self.scroll.line > 0 {
let line_i = self.scroll.line - 1;
if let Some(layout) = self.line_layout(font_system, line_i) {
let mut layout_height = 0.0;
for layout_line in layout.iter() {
layout_height +=
layout_line.line_height_opt.unwrap_or(metrics.line_height);
}
self.scroll.line = line_i;
self.scroll.vertical += layout_height;
2023-12-15 15:03:29 -07:00
}
} else {
self.scroll.vertical = 0.0;
break;
}
}
let scroll_start = self.scroll.vertical;
let scroll_end = scroll_start + self.height;
let mut total_height = 0.0;
for line_i in 0..self.lines.len() {
if line_i < self.scroll.line {
if prune {
self.lines[line_i].reset_shaping();
}
continue;
2023-12-15 09:48:06 -07:00
}
if total_height > scroll_end {
if prune {
self.lines[line_i].reset_shaping();
continue;
} else {
break;
}
}
let mut layout_height = 0.0;
let layout = self
.line_layout(font_system, line_i)
.expect("shape_until_scroll invalid line");
for layout_line in layout.iter() {
let line_height = layout_line.line_height_opt.unwrap_or(metrics.line_height);
layout_height += line_height;
total_height += line_height;
}
// Adjust scroll.vertical to be smaller by moving scroll.line forwards
//TODO: do we want to adjust it exactly to a layout line?
if line_i == self.scroll.line && layout_height < self.scroll.vertical {
self.scroll.line += 1;
self.scroll.vertical -= layout_height;
}
}
if total_height < scroll_end && self.scroll.line > 0 {
// Need to scroll up to stay inside of buffer
self.scroll.vertical -= scroll_end - total_height;
} else {
// Done adjusting scroll
break;
}
}
if old_scroll != self.scroll {
self.redraw = true;
}
2022-10-18 12:42:37 -06:00
}
2023-12-15 09:48:06 -07:00
/// Convert a [`Cursor`] to a [`LayoutCursor`]
pub fn layout_cursor(
&mut self,
font_system: &mut FontSystem,
cursor: Cursor,
) -> Option<LayoutCursor> {
2023-12-15 09:48:06 -07:00
let layout = self.line_layout(font_system, cursor.line)?;
2022-10-22 11:42:08 -06:00
for (layout_i, layout_line) in layout.iter().enumerate() {
for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
let cursor_end =
Cursor::new_with_affinity(cursor.line, glyph.end, Affinity::Before);
let cursor_start =
Cursor::new_with_affinity(cursor.line, glyph.start, Affinity::After);
2023-01-10 20:12:12 -07:00
let (cursor_left, cursor_right) = if glyph.level.is_ltr() {
(cursor_start, cursor_end)
} else {
(cursor_end, cursor_start)
};
2023-12-15 09:48:06 -07:00
if cursor == cursor_left {
return Some(LayoutCursor::new(cursor.line, layout_i, glyph_i));
2022-10-22 11:42:08 -06:00
}
2023-12-15 09:48:06 -07:00
if cursor == cursor_right {
return Some(LayoutCursor::new(cursor.line, layout_i, glyph_i + 1));
2022-10-22 11:42:08 -06:00
}
}
}
// Fall back to start of line
//TODO: should this be the end of the line?
2023-12-15 09:48:06 -07:00
Some(LayoutCursor::new(cursor.line, 0, 0))
2022-10-22 11:42:08 -06:00
}
2023-03-12 10:30:27 +01:00
/// Shape the provided line index and return the result
pub fn line_shape(
&mut self,
2023-03-12 10:30:20 +01:00
font_system: &mut FontSystem,
line_i: usize,
) -> Option<&ShapeLine> {
2022-10-31 11:24:36 -06:00
let line = self.lines.get_mut(line_i)?;
2024-06-06 21:01:46 -06:00
Some(line.shape_in_buffer(&mut self.scratch, font_system, self.tab_width))
2022-10-22 11:42:08 -06:00
}
2023-03-12 10:30:27 +01:00
/// Lay out the provided line index and return the result
pub fn line_layout(
&mut self,
2023-03-12 10:30:20 +01:00
font_system: &mut FontSystem,
line_i: usize,
) -> Option<&[LayoutLine]> {
2022-10-31 11:24:36 -06:00
let line = self.lines.get_mut(line_i)?;
Some(line.layout_in_buffer(
&mut self.scratch,
font_system,
self.metrics.font_size,
Some(self.width),
self.wrap,
self.monospace_width,
2024-06-06 21:01:46 -06:00
self.tab_width,
))
2022-10-19 08:34:34 -06:00
}
/// Get the current [`Metrics`]
2022-10-31 11:24:36 -06:00
pub fn metrics(&self) -> Metrics {
2022-10-18 17:04:22 -06:00
self.metrics
2022-10-18 12:07:22 -06:00
}
2023-03-12 10:30:27 +01:00
/// Set the current [`Metrics`]
///
/// # Panics
///
/// Will panic if `metrics.font_size` is zero.
pub fn set_metrics(&mut self, font_system: &mut FontSystem, metrics: Metrics) {
self.set_metrics_and_size(font_system, metrics, self.width, self.height);
2022-10-18 12:42:37 -06:00
}
/// Get the current [`Wrap`]
pub fn wrap(&self) -> Wrap {
self.wrap
}
2023-03-12 10:30:27 +01:00
/// Set the current [`Wrap`]
pub fn set_wrap(&mut self, font_system: &mut FontSystem, wrap: Wrap) {
if wrap != self.wrap {
self.wrap = wrap;
self.relayout(font_system);
self.shape_until_scroll(font_system, false);
}
}
/// Get the current `monospace_width`
pub fn monospace_width(&self) -> Option<f32> {
self.monospace_width
}
/// Set monospace width monospace glyphs should be resized to match. `None` means don't resize
2024-01-17 13:26:39 -07:00
pub fn set_monospace_width(
&mut self,
font_system: &mut FontSystem,
monospace_width: Option<f32>,
) {
if monospace_width != self.monospace_width {
self.monospace_width = monospace_width;
self.relayout(font_system);
self.shape_until_scroll(font_system, false);
}
}
2024-06-06 21:01:46 -06:00
/// Get the current `tab_width`
pub fn tab_width(&self) -> u16 {
self.tab_width
}
/// Set tab width (number of spaces between tab stops)
pub fn set_tab_width(&mut self, font_system: &mut FontSystem, tab_width: u16) {
// A tab width of 0 is not allowed
if tab_width == 0 {
return;
}
if tab_width != self.tab_width {
self.tab_width = tab_width;
// Shaping must be reset when tab width is changed
for line in self.lines.iter_mut() {
if line.shape_opt().is_some() {
if line.text().contains('\t') {
line.reset_shaping();
}
2024-06-06 21:01:46 -06:00
}
}
self.redraw = true;
self.shape_until_scroll(font_system, false);
}
}
2022-10-18 17:04:22 -06:00
/// Get the current buffer dimensions (width, height)
pub fn size(&self) -> (f32, f32) {
2022-10-18 17:04:22 -06:00
(self.width, self.height)
2022-10-18 13:05:36 -06:00
}
2023-03-12 10:30:27 +01:00
/// Set the current buffer dimensions
pub fn set_size(&mut self, font_system: &mut FontSystem, width: f32, height: f32) {
self.set_metrics_and_size(font_system, self.metrics, width, height);
}
/// Set the current [`Metrics`] and buffer dimensions at the same time
///
/// # Panics
///
/// Will panic if `metrics.font_size` is zero.
pub fn set_metrics_and_size(
&mut self,
font_system: &mut FontSystem,
metrics: Metrics,
width: f32,
height: f32,
) {
let clamped_width = width.max(0.0);
let clamped_height = height.max(0.0);
if metrics != self.metrics || clamped_width != self.width || clamped_height != self.height {
assert_ne!(metrics.font_size, 0.0, "font size cannot be 0");
self.metrics = metrics;
self.width = clamped_width;
self.height = clamped_height;
self.relayout(font_system);
self.shape_until_scroll(font_system, false);
2022-10-18 12:42:37 -06:00
}
2022-10-18 12:07:22 -06:00
}
2022-10-18 17:04:22 -06:00
/// Get the current scroll location
pub fn scroll(&self) -> Scroll {
2022-10-18 17:04:22 -06:00
self.scroll
}
2022-10-31 11:36:54 -06:00
/// Set the current scroll location
pub fn set_scroll(&mut self, scroll: Scroll) {
2022-10-31 11:24:36 -06:00
if scroll != self.scroll {
self.scroll = scroll;
self.redraw = true;
}
}
2023-03-12 10:30:27 +01:00
/// Set text of buffer, using provided attributes for each line by default
pub fn set_text(
&mut self,
font_system: &mut FontSystem,
text: &str,
attrs: Attrs,
shaping: Shaping,
) {
self.lines.clear();
for (range, ending) in LineIter::new(text) {
self.lines.push(BufferLine::new(
&text[range],
ending,
AttrsList::new(attrs),
shaping,
2024-04-30 12:21:21 -06:00
));
}
if self.lines.is_empty() {
self.lines.push(BufferLine::new(
"",
LineEnding::default(),
AttrsList::new(attrs),
shaping,
));
}
self.scroll = Scroll::default();
self.shape_until_scroll(font_system, false);
2023-06-27 21:53:05 +08:00
}
/// Set text of buffer, using an iterator of styled spans (pairs of text and attributes)
///
/// ```
/// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping};
/// # let mut font_system = FontSystem::new();
/// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0));
/// let attrs = Attrs::new().family(Family::Serif);
/// buffer.set_rich_text(
/// &mut font_system,
/// [
/// ("hello, ", attrs),
/// ("cosmic\ntext", attrs.family(Family::Monospace)),
/// ],
/// attrs,
2023-06-27 21:53:05 +08:00
/// Shaping::Advanced,
/// );
/// ```
pub fn set_rich_text<'r, 's, I>(
&mut self,
font_system: &mut FontSystem,
spans: I,
default_attrs: Attrs,
2023-06-27 21:53:05 +08:00
shaping: Shaping,
) where
I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
{
self.lines.clear();
2023-06-27 21:53:05 +08:00
let mut attrs_list = AttrsList::new(default_attrs);
2023-06-27 21:53:05 +08:00
let mut line_string = String::new();
let mut end = 0;
let (string, spans_data): (String, Vec<_>) = spans
.into_iter()
.map(|(s, attrs)| {
let start = end;
end += s.len();
(s, (attrs, start..end))
})
.unzip();
let mut spans_iter = spans_data.into_iter();
let mut maybe_span = spans_iter.next();
// split the string into lines, as ranges
let string_start = string.as_ptr() as usize;
let mut lines_iter = BidiParagraphs::new(&string).map(|line: &str| {
let start = line.as_ptr() as usize - string_start;
let end = start + line.len();
start..end
});
let mut maybe_line = lines_iter.next();
//TODO: set this based on information from spans
let line_ending = LineEnding::default();
2023-06-27 21:53:05 +08:00
loop {
let (Some(line_range), Some((attrs, span_range))) = (&maybe_line, &maybe_span) else {
// this is reached only if this text is empty
self.lines.push(BufferLine::new(
String::new(),
line_ending,
AttrsList::new(default_attrs),
2023-06-27 21:53:05 +08:00
shaping,
));
break;
};
2023-06-30 20:40:12 +08:00
// start..end is the intersection of this line and this span
2023-06-27 21:53:05 +08:00
let start = line_range.start.max(span_range.start);
let end = line_range.end.min(span_range.end);
if start < end {
let text = &string[start..end];
let text_start = line_string.len();
line_string.push_str(text);
let text_end = line_string.len();
// Only add attrs if they don't match the defaults
if *attrs != attrs_list.defaults() {
attrs_list.add_span(text_start..text_end, *attrs);
}
2023-06-27 21:53:05 +08:00
}
// we know that at the end of a line,
2023-06-30 20:40:12 +08:00
// span text's end index is always >= line text's end index
// so if this span ends before this line ends,
// there is another span in this line.
2023-06-27 21:53:05 +08:00
// otherwise, we move on to the next line.
if span_range.end < line_range.end {
maybe_span = spans_iter.next();
} else {
maybe_line = lines_iter.next();
if maybe_line.is_some() {
// finalize this line and start a new line
let prev_attrs_list =
core::mem::replace(&mut attrs_list, AttrsList::new(default_attrs));
let prev_line_string = core::mem::take(&mut line_string);
let buffer_line =
BufferLine::new(prev_line_string, line_ending, prev_attrs_list, shaping);
2023-06-27 21:53:05 +08:00
self.lines.push(buffer_line);
} else {
// finalize the final line
let buffer_line =
BufferLine::new(line_string, line_ending, attrs_list, shaping);
2023-06-27 21:53:05 +08:00
self.lines.push(buffer_line);
break;
}
}
2022-10-19 13:15:07 -06:00
}
self.scroll = Scroll::default();
self.shape_until_scroll(font_system, false);
2022-10-19 13:15:07 -06:00
}
/// True if a redraw is needed
pub fn redraw(&self) -> bool {
self.redraw
}
/// Set redraw needed flag
pub fn set_redraw(&mut self, redraw: bool) {
self.redraw = redraw;
}
2022-10-31 11:24:36 -06:00
/// Get the visible layout runs for rendering and other tasks
pub fn layout_runs(&self) -> LayoutRunIter {
2022-10-31 11:24:36 -06:00
LayoutRunIter::new(self)
2022-10-18 12:07:22 -06:00
}
2022-10-31 11:24:36 -06:00
/// Convert x, y position to Cursor (hit detection)
pub fn hit(&self, x: f32, y: f32) -> Option<Cursor> {
2023-01-23 10:57:06 -07:00
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
2022-11-08 08:43:27 -07:00
let instant = std::time::Instant::now();
2022-10-25 20:27:46 -06:00
let mut new_cursor_opt = None;
2022-10-25 13:13:13 -06:00
let mut runs = self.layout_runs().peekable();
let mut first_run = true;
while let Some(run) = runs.next() {
2024-06-06 15:21:44 -06:00
let line_top = run.line_top;
let line_height = run.line_height;
2022-10-25 13:13:13 -06:00
2024-06-06 15:21:44 -06:00
if first_run && y < line_top {
first_run = false;
let new_cursor = Cursor::new(run.line_i, 0);
new_cursor_opt = Some(new_cursor);
2024-06-06 15:21:44 -06:00
} else if y >= line_top && y < line_top + line_height {
2022-10-25 13:13:13 -06:00
let mut new_cursor_glyph = run.glyphs.len();
let mut new_cursor_char = 0;
let mut new_cursor_affinity = Affinity::After;
let mut first_glyph = true;
2022-10-25 13:13:13 -06:00
'hit: for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
if first_glyph {
first_glyph = false;
if (run.rtl && x > glyph.x) || (!run.rtl && x < 0.0) {
new_cursor_glyph = 0;
new_cursor_char = 0;
}
}
if x >= glyph.x && x <= glyph.x + glyph.w {
2022-10-25 13:13:13 -06:00
new_cursor_glyph = glyph_i;
let cluster = &run.text[glyph.start..glyph.end];
let total = cluster.grapheme_indices(true).count();
let mut egc_x = glyph.x;
let egc_w = glyph.w / (total as f32);
for (egc_i, egc) in cluster.grapheme_indices(true) {
if x >= egc_x && x <= egc_x + egc_w {
2022-10-25 13:13:13 -06:00
new_cursor_char = egc_i;
let right_half = x >= egc_x + egc_w / 2.0;
2022-12-16 16:49:29 -07:00
if right_half != glyph.level.is_rtl() {
2022-10-25 13:13:13 -06:00
// If clicking on last half of glyph, move cursor past glyph
new_cursor_char += egc.len();
new_cursor_affinity = Affinity::Before;
2022-10-24 14:18:40 -06:00
}
2022-10-25 13:13:13 -06:00
break 'hit;
2022-10-24 14:18:40 -06:00
}
2022-10-25 13:13:13 -06:00
egc_x += egc_w;
}
2022-10-24 14:18:40 -06:00
let right_half = x >= glyph.x + glyph.w / 2.0;
2022-12-16 16:49:29 -07:00
if right_half != glyph.level.is_rtl() {
2022-10-25 13:13:13 -06:00
// If clicking on last half of glyph, move cursor past glyph
new_cursor_char = cluster.len();
new_cursor_affinity = Affinity::Before;
}
2022-10-25 13:13:13 -06:00
break 'hit;
}
2022-10-25 13:13:13 -06:00
}
2022-10-31 11:24:36 -06:00
let mut new_cursor = Cursor::new(run.line_i, 0);
2022-10-25 13:13:13 -06:00
match run.glyphs.get(new_cursor_glyph) {
Some(glyph) => {
// Position at glyph
new_cursor.index = glyph.start + new_cursor_char;
2023-01-10 20:12:12 -07:00
new_cursor.affinity = new_cursor_affinity;
2023-01-04 20:03:03 -07:00
}
None => {
if let Some(glyph) = run.glyphs.last() {
// Position at end of line
new_cursor.index = glyph.end;
new_cursor.affinity = Affinity::Before;
2023-01-04 20:03:03 -07:00
}
}
2022-10-25 13:13:13 -06:00
}
2022-10-25 20:27:46 -06:00
new_cursor_opt = Some(new_cursor);
2022-10-25 13:13:13 -06:00
break;
} else if runs.peek().is_none() && y > run.line_y {
let mut new_cursor = Cursor::new(run.line_i, 0);
if let Some(glyph) = run.glyphs.last() {
2023-01-10 20:12:12 -07:00
new_cursor = run.cursor_from_glyph_right(glyph);
}
new_cursor_opt = Some(new_cursor);
}
}
2023-01-23 10:57:06 -07:00
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
2022-11-08 08:43:27 -07:00
log::trace!("click({}, {}): {:?}", x, y, instant.elapsed());
2022-10-25 20:27:46 -06:00
new_cursor_opt
}
/// Apply a [`Motion`] to a [`Cursor`]
pub fn cursor_motion(
&mut self,
font_system: &mut FontSystem,
mut cursor: Cursor,
2023-12-15 15:03:29 -07:00
mut cursor_x_opt: Option<i32>,
motion: Motion,
2023-12-15 15:03:29 -07:00
) -> Option<(Cursor, Option<i32>)> {
match motion {
Motion::LayoutCursor(layout_cursor) => {
let layout = self.line_layout(font_system, layout_cursor.line)?;
let layout_line = match layout.get(layout_cursor.layout) {
Some(some) => some,
None => match layout.last() {
Some(some) => some,
2023-12-15 15:03:29 -07:00
None => {
return None;
}
},
};
let (new_index, new_affinity) = match layout_line.glyphs.get(layout_cursor.glyph) {
Some(glyph) => (glyph.start, Affinity::After),
None => match layout_line.glyphs.last() {
Some(glyph) => (glyph.end, Affinity::Before),
//TODO: is this correct?
None => (0, Affinity::After),
},
};
if cursor.line != layout_cursor.line
|| cursor.index != new_index
|| cursor.affinity != new_affinity
{
cursor.line = layout_cursor.line;
cursor.index = new_index;
cursor.affinity = new_affinity;
}
}
Motion::Previous => {
let line = self.lines.get(cursor.line)?;
if cursor.index > 0 {
// Find previous character index
let mut prev_index = 0;
for (i, _) in line.text().grapheme_indices(true) {
if i < cursor.index {
prev_index = i;
} else {
break;
}
}
cursor.index = prev_index;
cursor.affinity = Affinity::After;
} else if cursor.line > 0 {
cursor.line -= 1;
cursor.index = self.lines.get(cursor.line)?.text().len();
cursor.affinity = Affinity::After;
}
2023-12-15 15:03:29 -07:00
cursor_x_opt = None;
}
Motion::Next => {
let line = self.lines.get(cursor.line)?;
if cursor.index < line.text().len() {
for (i, c) in line.text().grapheme_indices(true) {
if i == cursor.index {
cursor.index += c.len();
cursor.affinity = Affinity::Before;
break;
}
}
} else if cursor.line + 1 < self.lines.len() {
cursor.line += 1;
cursor.index = 0;
cursor.affinity = Affinity::Before;
}
2023-12-15 15:03:29 -07:00
cursor_x_opt = None;
}
Motion::Left => {
let rtl_opt = self
.line_shape(font_system, cursor.line)
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) =
self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Next)?;
} else {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::Previous,
)?;
}
}
}
Motion::Right => {
let rtl_opt = self
.line_shape(font_system, cursor.line)
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::Previous,
)?;
} else {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) =
self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Next)?;
}
}
}
Motion::Up => {
2023-12-15 09:48:06 -07:00
let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
2023-12-15 15:03:29 -07:00
if cursor_x_opt.is_none() {
cursor_x_opt = Some(
layout_cursor.glyph as i32, //TODO: glyph x position
);
}
if layout_cursor.layout > 0 {
layout_cursor.layout -= 1;
} else if layout_cursor.line > 0 {
layout_cursor.line -= 1;
layout_cursor.layout = usize::max_value();
}
2023-12-15 15:03:29 -07:00
if let Some(cursor_x) = cursor_x_opt {
layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position
}
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::LayoutCursor(layout_cursor),
)?;
}
Motion::Down => {
2023-12-15 09:48:06 -07:00
let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
let layout_len = self.line_layout(font_system, layout_cursor.line)?.len();
2023-12-15 15:03:29 -07:00
if cursor_x_opt.is_none() {
cursor_x_opt = Some(
layout_cursor.glyph as i32, //TODO: glyph x position
);
}
if layout_cursor.layout + 1 < layout_len {
layout_cursor.layout += 1;
} else if layout_cursor.line + 1 < self.lines.len() {
layout_cursor.line += 1;
layout_cursor.layout = 0;
}
2023-12-15 15:03:29 -07:00
if let Some(cursor_x) = cursor_x_opt {
layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position
}
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::LayoutCursor(layout_cursor),
)?;
}
Motion::Home => {
2023-12-15 09:48:06 -07:00
let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
layout_cursor.glyph = 0;
2023-12-15 15:03:29 -07:00
#[allow(unused_assignments)]
{
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::LayoutCursor(layout_cursor),
)?;
}
cursor_x_opt = None;
}
Motion::SoftHome => {
let line = self.lines.get(cursor.line)?;
cursor.index = line
.text()
.char_indices()
.filter_map(|(i, c)| if c.is_whitespace() { None } else { Some(i) })
.next()
.unwrap_or(0);
2023-12-15 15:03:29 -07:00
cursor_x_opt = None;
}
Motion::End => {
2023-12-15 09:48:06 -07:00
let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
layout_cursor.glyph = usize::max_value();
2023-12-15 15:03:29 -07:00
#[allow(unused_assignments)]
{
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::LayoutCursor(layout_cursor),
)?;
}
cursor_x_opt = None;
}
Motion::ParagraphStart => {
cursor.index = 0;
2023-12-15 15:03:29 -07:00
cursor_x_opt = None;
}
Motion::ParagraphEnd => {
cursor.index = self.lines.get(cursor.line)?.text().len();
2023-12-15 15:03:29 -07:00
cursor_x_opt = None;
}
Motion::PageUp => {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
2023-12-15 15:03:29 -07:00
cursor_x_opt,
Motion::Vertical(-self.size().1 as i32),
)?;
}
Motion::PageDown => {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
2023-12-15 15:03:29 -07:00
cursor_x_opt,
Motion::Vertical(self.size().1 as i32),
)?;
}
Motion::Vertical(px) => {
2024-06-06 15:21:44 -06:00
// TODO more efficient, use layout run line height
let lines = px / self.metrics().line_height as i32;
match lines.cmp(&0) {
cmp::Ordering::Less => {
for _ in 0..-lines {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) =
self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Up)?;
}
}
cmp::Ordering::Greater => {
for _ in 0..lines {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::Down,
)?;
}
}
cmp::Ordering::Equal => {}
}
}
Motion::PreviousWord => {
let line = self.lines.get(cursor.line)?;
if cursor.index > 0 {
cursor.index = line
.text()
.unicode_word_indices()
.rev()
.map(|(i, _)| i)
.find(|&i| i < cursor.index)
.unwrap_or(0);
} else if cursor.line > 0 {
cursor.line -= 1;
cursor.index = self.lines.get(cursor.line)?.text().len();
}
2023-12-15 15:03:29 -07:00
cursor_x_opt = None;
}
Motion::NextWord => {
let line = self.lines.get(cursor.line)?;
if cursor.index < line.text().len() {
cursor.index = line
.text()
.unicode_word_indices()
.map(|(i, word)| i + word.len())
.find(|&i| i > cursor.index)
.unwrap_or(line.text().len());
} else if cursor.line + 1 < self.lines.len() {
cursor.line += 1;
cursor.index = 0;
}
2023-12-15 15:03:29 -07:00
cursor_x_opt = None;
}
Motion::LeftWord => {
let rtl_opt = self
.line_shape(font_system, cursor.line)
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::NextWord,
)?;
} else {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::PreviousWord,
)?;
}
}
}
Motion::RightWord => {
let rtl_opt = self
.line_shape(font_system, cursor.line)
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::PreviousWord,
)?;
} else {
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::NextWord,
)?;
}
}
}
Motion::BufferStart => {
cursor.line = 0;
cursor.index = 0;
2023-12-15 15:03:29 -07:00
cursor_x_opt = None;
}
Motion::BufferEnd => {
cursor.line = self.lines.len() - 1;
cursor.index = self.lines.get(cursor.line)?.text().len();
2023-12-15 15:03:29 -07:00
cursor_x_opt = None;
}
Motion::GotoLine(line) => {
2023-12-15 09:48:06 -07:00
let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
layout_cursor.line = line;
2023-12-15 15:03:29 -07:00
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::LayoutCursor(layout_cursor),
)?;
}
}
2023-12-15 15:03:29 -07:00
Some((cursor, cursor_x_opt))
}
2023-03-12 10:30:27 +01:00
/// Draw the buffer
2022-10-25 11:40:10 -06:00
#[cfg(feature = "swash")]
2023-03-12 10:30:27 +01:00
pub fn draw<F>(
&self,
2023-03-12 10:30:20 +01:00
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
mut f: F,
) where
2023-01-04 20:03:03 -07:00
F: FnMut(i32, i32, u32, u32, Color),
2022-10-25 11:40:10 -06:00
{
2022-10-25 12:52:46 -06:00
for run in self.layout_runs() {
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((0., 0.), 1.0);
2022-10-26 14:16:48 -06:00
let glyph_color = match glyph.color_opt {
2022-10-27 09:07:47 -06:00
Some(some) => some,
2022-10-26 14:16:48 -06:00
None => color,
};
cache.with_pixels(
font_system,
physical_glyph.cache_key,
glyph_color,
|x, y, color| {
f(
physical_glyph.x + x,
run.line_y as i32 + physical_glyph.y + y,
1,
1,
color,
);
},
);
}
2022-10-25 12:52:46 -06:00
}
}
2022-10-18 12:07:22 -06:00
}
impl<'a> BorrowedWithFontSystem<'a, Buffer> {
/// Shape lines until cursor, also scrolling to include cursor in view
pub fn shape_until_cursor(&mut self, cursor: Cursor, prune: bool) {
self.inner
.shape_until_cursor(self.font_system, cursor, prune);
}
/// Shape lines until scroll
pub fn shape_until_scroll(&mut self, prune: bool) {
self.inner.shape_until_scroll(self.font_system, prune);
}
/// Shape the provided line index and return the result
pub fn line_shape(&mut self, line_i: usize) -> Option<&ShapeLine> {
self.inner.line_shape(self.font_system, line_i)
}
/// Lay out the provided line index and return the result
pub fn line_layout(&mut self, line_i: usize) -> Option<&[LayoutLine]> {
self.inner.line_layout(self.font_system, line_i)
}
/// Set the current [`Metrics`]
///
/// # Panics
///
/// Will panic if `metrics.font_size` is zero.
pub fn set_metrics(&mut self, metrics: Metrics) {
self.inner.set_metrics(self.font_system, metrics);
}
/// Set the current [`Wrap`]
pub fn set_wrap(&mut self, wrap: Wrap) {
self.inner.set_wrap(self.font_system, wrap);
}
/// Set the current buffer dimensions
pub fn set_size(&mut self, width: f32, height: f32) {
self.inner.set_size(self.font_system, width, height);
}
/// Set the current [`Metrics`] and buffer dimensions at the same time
///
/// # Panics
///
/// Will panic if `metrics.font_size` is zero.
pub fn set_metrics_and_size(&mut self, metrics: Metrics, width: f32, height: f32) {
self.inner
.set_metrics_and_size(self.font_system, metrics, width, height);
}
/// Set text of buffer, using provided attributes for each line by default
pub fn set_text(&mut self, text: &str, attrs: Attrs, shaping: Shaping) {
self.inner.set_text(self.font_system, text, attrs, shaping);
}
/// Set text of buffer, using an iterator of styled spans (pairs of text and attributes)
///
/// ```
/// # use cosmic_text::{Attrs, Buffer, Family, FontSystem, Metrics, Shaping};
/// # let mut font_system = FontSystem::new();
/// let mut buffer = Buffer::new_empty(Metrics::new(32.0, 44.0));
/// let mut buffer = buffer.borrow_with(&mut font_system);
/// let attrs = Attrs::new().family(Family::Serif);
/// buffer.set_rich_text(
/// [
/// ("hello, ", attrs),
/// ("cosmic\ntext", attrs.family(Family::Monospace)),
/// ],
/// attrs,
/// Shaping::Advanced,
/// );
/// ```
pub fn set_rich_text<'r, 's, I>(&mut self, spans: I, default_attrs: Attrs, shaping: Shaping)
where
I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
{
self.inner
.set_rich_text(self.font_system, spans, default_attrs, shaping);
}
/// Apply a [`Motion`] to a [`Cursor`]
2023-12-15 15:03:29 -07:00
pub fn cursor_motion(
&mut self,
cursor: Cursor,
cursor_x_opt: Option<i32>,
motion: Motion,
) -> Option<(Cursor, Option<i32>)> {
self.inner
.cursor_motion(self.font_system, cursor, cursor_x_opt, motion)
}
/// Draw the buffer
#[cfg(feature = "swash")]
2023-03-12 10:30:20 +01:00
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
self.inner.draw(self.font_system, cache, color, f);
}
}