feat: add TextDecoration rendering
This commit is contained in:
parent
2edae7ef1d
commit
2758919c80
6 changed files with 136 additions and 10 deletions
|
|
@ -10,9 +10,9 @@ use core_maths::CoreFloat;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color,
|
render_decoration, Affinity, Align, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem,
|
||||||
Cursor, Ellipsize, FontSystem, Hinting, LayoutCursor, LayoutGlyph, LayoutLine, LineEnding,
|
BufferLine, Color, Cursor, Ellipsize, FontSystem, Hinting, LayoutCursor, LayoutGlyph,
|
||||||
LineIter, Motion, Renderer, Scroll, ShapeLine, Shaping, Wrap,
|
LayoutLine, LineEnding, LineIter, Motion, Renderer, Scroll, ShapeLine, Shaping, Wrap,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A line of visible text for rendering
|
/// A line of visible text for rendering
|
||||||
|
|
@ -1385,6 +1385,8 @@ impl Buffer {
|
||||||
|
|
||||||
pub fn render<R: Renderer>(&self, renderer: &mut R, color: Color) {
|
pub fn render<R: Renderer>(&self, renderer: &mut R, color: Color) {
|
||||||
for run in self.layout_runs() {
|
for run in self.layout_runs() {
|
||||||
|
// draw decorations before glyphs so text renders on top
|
||||||
|
render_decoration(renderer, &run, color);
|
||||||
for glyph in run.glyphs {
|
for glyph in run.glyphs {
|
||||||
let physical_glyph = glyph.physical((0., run.line_y), 1.0);
|
let physical_glyph = glyph.physical((0., run.line_y), 1.0);
|
||||||
let glyph_color = glyph.color_opt.map_or(color, |some| some);
|
let glyph_color = glyph.color_opt.map_or(color, |some| some);
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ use core::cmp;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, Attrs, AttrsList, BorrowedWithFontSystem, BufferLine, BufferRef, Change, ChangeItem,
|
render_decoration, Action, Attrs, AttrsList, BorrowedWithFontSystem, BufferLine, BufferRef,
|
||||||
Color, Cursor, Edit, FontSystem, LayoutRun, LineEnding, LineIter, Renderer, Selection, Shaping,
|
Change, ChangeItem, Color, Cursor, Edit, FontSystem, LayoutRun, LineEnding, LineIter, Renderer,
|
||||||
|
Selection, Shaping,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A wrapper of [`Buffer`] for easy editing
|
/// A wrapper of [`Buffer`] for easy editing
|
||||||
|
|
@ -211,6 +212,7 @@ impl<'buffer> Editor<'buffer> {
|
||||||
renderer.rectangle(x, y, 1, line_height as u32, cursor_color);
|
renderer.rectangle(x, y, 1, line_height as u32, cursor_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render_decoration(renderer, &run, text_color);
|
||||||
for glyph in run.glyphs {
|
for glyph in run.glyphs {
|
||||||
let physical_glyph = glyph.physical((0., line_y), 1.0);
|
let physical_glyph = glyph.physical((0., line_y), 1.0);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AttrsList, BorrowedWithFontSystem, BufferRef, Change, Color, Cursor, Edit, Editor,
|
Action, AttrsList, BorrowedWithFontSystem, BufferRef, Change, Color, Cursor, Edit, Editor,
|
||||||
FontSystem, Renderer, Selection, Shaping, Style, Weight,
|
FontSystem, Renderer, Selection, Shaping, Style, UnderlineStyle, Weight,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use syntect::highlighting::Theme as SyntaxTheme;
|
pub use syntect::highlighting::Theme as SyntaxTheme;
|
||||||
|
|
@ -366,7 +366,12 @@ impl<'buffer> Edit<'buffer> for SyntaxEditor<'_, 'buffer> {
|
||||||
Weight::BOLD
|
Weight::BOLD
|
||||||
} else {
|
} else {
|
||||||
Weight::NORMAL
|
Weight::NORMAL
|
||||||
}); //TODO: underline
|
})
|
||||||
|
.underline(if style.font_style.contains(FontStyle::UNDERLINE) {
|
||||||
|
UnderlineStyle::Single
|
||||||
|
} else {
|
||||||
|
UnderlineStyle::None
|
||||||
|
});
|
||||||
if span_attrs != original_attrs {
|
if span_attrs != original_attrs {
|
||||||
attrs_list.add_span(range, &span_attrs);
|
attrs_list.add_span(range, &span_attrs);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use core::fmt::Display;
|
use core::fmt::Display;
|
||||||
|
|
||||||
use crate::{math, CacheKey, CacheKeyFlags, Color};
|
use crate::{math, CacheKey, CacheKeyFlags, Color, TextDecoration};
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
|
@ -58,6 +58,8 @@ pub struct LayoutGlyph {
|
||||||
pub metadata: usize,
|
pub metadata: usize,
|
||||||
/// [`CacheKeyFlags`]
|
/// [`CacheKeyFlags`]
|
||||||
pub cache_key_flags: CacheKeyFlags,
|
pub cache_key_flags: CacheKeyFlags,
|
||||||
|
/// Text decoration (underline, strikethough, overline)
|
||||||
|
pub text_decoration: TextDecoration,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
||||||
113
src/render.rs
113
src/render.rs
|
|
@ -1,6 +1,6 @@
|
||||||
//! Helpers for rendering buffers and editors
|
//! Helpers for rendering buffers and editors
|
||||||
|
|
||||||
use crate::{Color, PhysicalGlyph};
|
use crate::{Color, LayoutGlyph, LayoutRun, PhysicalGlyph, TextDecoration, UnderlineStyle};
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
use crate::{FontSystem, SwashCache};
|
use crate::{FontSystem, SwashCache};
|
||||||
|
|
||||||
|
|
@ -14,6 +14,117 @@ pub trait Renderer {
|
||||||
fn glyph(&mut self, physical_glyph: PhysicalGlyph, color: Color);
|
fn glyph(&mut self, physical_glyph: PhysicalGlyph, color: Color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_decoration<R: Renderer>(renderer: &mut R, run: &LayoutRun, default_color: Color) {
|
||||||
|
if run.glyphs.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut group_start: Option<usize> = None;
|
||||||
|
|
||||||
|
for (i, glyph) in run.glyphs.iter().enumerate() {
|
||||||
|
let start_new_group = match group_start {
|
||||||
|
None => true,
|
||||||
|
Some(_) => {
|
||||||
|
let prev = &run.glyphs[i - 1];
|
||||||
|
glyph.text_decoration != prev.text_decoration
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if start_new_group {
|
||||||
|
if let Some(gs) = group_start {
|
||||||
|
draw_decoration_group(renderer, run, &run.glyphs[gs..i], default_color);
|
||||||
|
}
|
||||||
|
group_start = if has_any_decoration(&glyph.text_decoration) {
|
||||||
|
Some(i)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(gs) = group_start {
|
||||||
|
draw_decoration_group(renderer, run, &run.glyphs[gs..], default_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_any_decoration(td: &TextDecoration) -> bool {
|
||||||
|
td.underline != UnderlineStyle::None || td.overline || td.strikethrough
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_decoration_group<R: Renderer>(
|
||||||
|
renderer: &mut R,
|
||||||
|
run: &LayoutRun,
|
||||||
|
glyphs: &[LayoutGlyph],
|
||||||
|
default_color: Color,
|
||||||
|
) {
|
||||||
|
if glyphs.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let first = &glyphs[0];
|
||||||
|
let last = &glyphs[glyphs.len() - 1];
|
||||||
|
let td = &glyphs[0].text_decoration;
|
||||||
|
let font_size = first.font_size;
|
||||||
|
let x_start = first.x;
|
||||||
|
let x_end = last.x + last.w;
|
||||||
|
let width = (x_end - x_start) as u32;
|
||||||
|
if width <= 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Underline
|
||||||
|
match td.underline {
|
||||||
|
UnderlineStyle::None => {}
|
||||||
|
UnderlineStyle::Single => {
|
||||||
|
let color = td
|
||||||
|
.underline_color_opt
|
||||||
|
.or(first.color_opt)
|
||||||
|
.unwrap_or(default_color);
|
||||||
|
let thickness = (font_size * 14.0).max(1.0);
|
||||||
|
let y = run.line_y + font_size * 0.125;
|
||||||
|
renderer.rectangle(x_start as i32, y as i32, width, thickness as u32, color);
|
||||||
|
}
|
||||||
|
UnderlineStyle::Double => {
|
||||||
|
let color = td
|
||||||
|
.underline_color_opt
|
||||||
|
.or(first.color_opt)
|
||||||
|
.unwrap_or(default_color);
|
||||||
|
let thickness = (font_size * 14.0).max(1.0);
|
||||||
|
let gap = thickness;
|
||||||
|
let y = run.line_y + font_size * 0.125;
|
||||||
|
renderer.rectangle(x_start as i32, y as i32, width, thickness as u32, color);
|
||||||
|
renderer.rectangle(
|
||||||
|
x_start as i32,
|
||||||
|
(y + thickness + gap) as i32,
|
||||||
|
width,
|
||||||
|
thickness as u32,
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strikethrough
|
||||||
|
if td.strikethrough {
|
||||||
|
let color = td
|
||||||
|
.strikethrough_color_opt
|
||||||
|
.or(first.color_opt)
|
||||||
|
.unwrap_or(default_color);
|
||||||
|
let thickness = (font_size / 14.0).max(1.0);
|
||||||
|
let y = run.line_y - font_size * 0.3;
|
||||||
|
renderer.rectangle(x_start as i32, y as i32, width, thickness as u32, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overline
|
||||||
|
if td.overline {
|
||||||
|
let color = td
|
||||||
|
.overline_color_opt
|
||||||
|
.or(first.color_opt)
|
||||||
|
.unwrap_or(default_color);
|
||||||
|
let thickness = (font_size / 14.0).max(1.0);
|
||||||
|
let y = run.line_top;
|
||||||
|
renderer.rectangle(x_start as i32, y as i32, width, thickness as u32, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper to migrate from old renderer
|
/// Helper to migrate from old renderer
|
||||||
//TODO: remove in future version
|
//TODO: remove in future version
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
use crate::fallback::FontFallbackIter;
|
use crate::fallback::FontFallbackIter;
|
||||||
use crate::{
|
use crate::{
|
||||||
math, Align, Attrs, AttrsList, CacheKeyFlags, Color, Ellipsize, EllipsizeHeightLimit, Font,
|
math, Align, Attrs, AttrsList, CacheKeyFlags, Color, Ellipsize, EllipsizeHeightLimit, Font,
|
||||||
FontSystem, Hinting, LayoutGlyph, LayoutLine, Metrics, Wrap,
|
FontSystem, Hinting, LayoutGlyph, LayoutLine, Metrics, TextDecoration, Wrap,
|
||||||
};
|
};
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::{format, vec, vec::Vec};
|
use alloc::{format, vec, vec::Vec};
|
||||||
|
|
@ -236,6 +236,7 @@ fn shape_fallback(
|
||||||
metadata: attrs.metadata,
|
metadata: attrs.metadata,
|
||||||
cache_key_flags: override_fake_italic(attrs.cache_key_flags, font, &attrs),
|
cache_key_flags: override_fake_italic(attrs.cache_key_flags, font, &attrs),
|
||||||
metrics_opt: attrs.metrics_opt.map(Into::into),
|
metrics_opt: attrs.metrics_opt.map(Into::into),
|
||||||
|
text_decoration: attrs.text_decoration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -536,6 +537,7 @@ fn shape_skip(
|
||||||
&attrs,
|
&attrs,
|
||||||
),
|
),
|
||||||
metrics_opt: attrs.metrics_opt.map(Into::into),
|
metrics_opt: attrs.metrics_opt.map(Into::into),
|
||||||
|
text_decoration: attrs.text_decoration,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -572,6 +574,7 @@ pub struct ShapeGlyph {
|
||||||
pub metadata: usize,
|
pub metadata: usize,
|
||||||
pub cache_key_flags: CacheKeyFlags,
|
pub cache_key_flags: CacheKeyFlags,
|
||||||
pub metrics_opt: Option<Metrics>,
|
pub metrics_opt: Option<Metrics>,
|
||||||
|
pub text_decoration: TextDecoration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShapeGlyph {
|
impl ShapeGlyph {
|
||||||
|
|
@ -601,6 +604,7 @@ impl ShapeGlyph {
|
||||||
color_opt: self.color_opt,
|
color_opt: self.color_opt,
|
||||||
metadata: self.metadata,
|
metadata: self.metadata,
|
||||||
cache_key_flags: self.cache_key_flags,
|
cache_key_flags: self.cache_key_flags,
|
||||||
|
text_decoration: self.text_decoration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue