Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for colored underlines and strikethroughs #225

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions src/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,12 +329,9 @@ impl Renderer {
bounds,
self.zoom,
) {
let min = (line.0 .0, line.0 .1);
let max = (line.1 .0, line.1 .1 + 2. * self.hidpi_scale * self.zoom);
self.draw_rectangle(
Rect::from_min_max(min, max),
native_color(self.theme.text_color, &self.surface_format),
)?;
let min = (line.min.0, line.min.1);
let max = (line.max.0, line.max.1 + 2. * self.hidpi_scale * self.zoom);
self.draw_rectangle(Rect::from_min_max(min, max), line.color)?;
}
if let Some(selection) = self.selection {
let (selection_rects, selection_text) = text_box.render_selection(
Expand Down
101 changes: 67 additions & 34 deletions src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ use std::borrow::BorrowMut;
use std::collections::hash_map;
use std::fmt;
use std::hash::{BuildHasher, Hash, Hasher};
use std::ops::Range;
use std::sync::{Arc, Mutex};

use crate::debug_impls::{self, DebugInline, DebugInlineMaybeF32Color};
use crate::utils::{Align, Line, Point, Rect, Selection, Size};

use fxhash::{FxHashMap, FxHashSet};
use glyphon::{
Affinity, Attrs, AttrsList, BufferLine, Color, Cursor, FamilyOwned, FontSystem, Shaping, Style,
SwashCache, TextArea, TextBounds, Weight,
Affinity, Attrs, AttrsList, BufferLine, Color, Cursor, FamilyOwned, FontSystem, LayoutGlyph,
Shaping, Style, SwashCache, TextArea, TextBounds, Weight,
};
use smart_debug::SmartDebug;
use taffy::prelude::{AvailableSpace, Size as TaffySize};
Expand Down Expand Up @@ -297,13 +298,31 @@ impl TextBox {
bounds: Size,
zoom: f32,
) -> Vec<Line> {
let mut has_lines = false;
for text in &self.texts {
if text.is_striked || text.is_underlined {
has_lines = true;
break;
}
fn push_line_segment(
lines: &mut Vec<ThinLine>,
current_line: Option<ThinLine>,
glyph: &LayoutGlyph,
color: [f32; 4],
) -> ThinLine {
let range = if let Some(current) = current_line {
if current.color == color {
let mut range = current.range;
range.end = glyph.end;
range
} else {
lines.push(current);
glyph.start..glyph.end
}
} else {
glyph.start..glyph.end
};
ThinLine { range, color }
}

let has_lines = self
.texts
.iter()
.any(|text| text.is_striked || text.is_underlined);
if !has_lines {
return Vec::new();
}
Expand All @@ -320,48 +339,56 @@ impl TextBox {

let mut y = screen_position.1 + line_height;
for line in buffer.layout_runs() {
let mut underline_ranges = Vec::new();
let mut underline_range = None;
let mut strike_ranges = Vec::new();
let mut strike_range = None;
let mut underlines = Vec::new();
let mut current_underline: Option<ThinLine> = None;
let mut strikes = Vec::new();
let mut current_strike: Option<ThinLine> = None;
// Goes over glyphs and finds the underlines and strikethroughs. The current
// underline/strikethrough is combined with matching consecutive lines
for glyph in line.glyphs {
let text = &self.texts[glyph.metadata];
let color = text.color.unwrap_or(text.default_color);
if text.is_underlined {
let mut range = underline_range.unwrap_or(glyph.start..glyph.end);
range.end = glyph.end;
underline_range = Some(range);
} else if let Some(range) = underline_range.clone() {
underline_ranges.push(range);
let underline =
push_line_segment(&mut underlines, current_underline, glyph, color);
current_underline = Some(underline);
} else if let Some(current) = current_underline.clone() {
underlines.push(current);
}
if text.is_striked {
let mut range = strike_range.unwrap_or(glyph.start..glyph.end);
range.end = glyph.end;
strike_range = Some(range);
} else if let Some(range) = strike_range.clone() {
strike_ranges.push(range);
let strike = push_line_segment(&mut strikes, current_strike, glyph, color);
current_strike = Some(strike);
} else if let Some(current) = current_strike.clone() {
strikes.push(current);
}
}
if let Some(range) = underline_range.clone() {
underline_ranges.push(range);
if let Some(current) = current_underline.take() {
underlines.push(current);
}
if let Some(range) = strike_range.clone() {
strike_ranges.push(range);
if let Some(current) = current_strike.take() {
strikes.push(current);
}
for underline_range in &underline_ranges {
let start_cursor = Cursor::new(line.line_i, underline_range.start);
let end_cursor = Cursor::new(line.line_i, underline_range.end);
for ThinLine { range, color } in &underlines {
let start_cursor = Cursor::new(line.line_i, range.start);
let end_cursor = Cursor::new(line.line_i, range.end);
if let Some((highlight_x, highlight_w)) = line.highlight(start_cursor, end_cursor) {
let x = screen_position.0 + highlight_x;
lines.push(((x.floor(), y), ((x + highlight_w).ceil(), y)));
let min = (x.floor(), y);
let max = ((x + highlight_w).ceil(), y);
let line = Line::with_color(min, max, *color);
lines.push(line);
}
}
for strike_range in &strike_ranges {
let start_cursor = Cursor::new(line.line_i, strike_range.start);
let end_cursor = Cursor::new(line.line_i, strike_range.end);
for ThinLine { range, color } in &strikes {
let start_cursor = Cursor::new(line.line_i, range.start);
let end_cursor = Cursor::new(line.line_i, range.end);
if let Some((highlight_x, highlight_w)) = line.highlight(start_cursor, end_cursor) {
let x = screen_position.0 + highlight_x;
let y = y - (line_height / 2.);
lines.push(((x.floor(), y), ((x + highlight_w).ceil(), y)));
let min = (x.floor(), y);
let max = ((x + highlight_w).ceil(), y);
let line = Line::with_color(min, max, *color);
lines.push(line);
}
}
y += line_height;
Expand Down Expand Up @@ -455,6 +482,12 @@ impl TextBox {
}
}

#[derive(Clone)]
struct ThinLine {
range: Range<usize>,
color: [f32; 4],
}

#[derive(Clone)]
pub struct Text {
pub text: String,
Expand Down
14 changes: 13 additions & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,24 @@ pub fn usize_in_mib(num: usize) -> f32 {
num as f32 / 1_024.0 / 1_024.0
}

pub type Line = ((f32, f32), (f32, f32));
pub type Selection = ((f32, f32), (f32, f32));
pub type Point = (f32, f32);
pub type Size = (f32, f32);
pub type ImageCache = Arc<Mutex<HashMap<String, Arc<Mutex<Option<ImageData>>>>>>;

#[derive(Debug, Clone)]
pub struct Line {
pub min: Point,
pub max: Point,
pub color: [f32; 4],
}

impl Line {
pub fn with_color(min: Point, max: Point, color: [f32; 4]) -> Self {
Self { min, max, color }
}
}

#[derive(Debug, Clone)]
pub struct Rect {
pub pos: Point,
Expand Down