Start on text highlight support
This commit is contained in:
parent
3dc100adfb
commit
ac1eb19f83
15 changed files with 198 additions and 91 deletions
|
@ -5,7 +5,7 @@ use std::{
|
|||
};
|
||||
|
||||
use crate::json::ToJson;
|
||||
use pathfinder_color::ColorU;
|
||||
use pathfinder_color::{ColorF, ColorU};
|
||||
use serde::{
|
||||
de::{self, Unexpected},
|
||||
Deserialize, Deserializer,
|
||||
|
@ -48,6 +48,29 @@ impl Color {
|
|||
pub fn from_u32(rgba: u32) -> Self {
|
||||
Self(ColorU::from_u32(rgba))
|
||||
}
|
||||
|
||||
pub fn blend(source: Color, dest: Color) -> Color {
|
||||
if dest.a == 255 {
|
||||
return dest;
|
||||
}
|
||||
|
||||
let source = source.0.to_f32();
|
||||
let dest = dest.0.to_f32();
|
||||
|
||||
let a = source.a() + (dest.a() * (1. - source.a()));
|
||||
let r = ((source.r() * source.a()) + (dest.r() * dest.a() * (1. - source.a()))) / a;
|
||||
let g = ((source.g() * source.a()) + (dest.g() * dest.a() * (1. - source.a()))) / a;
|
||||
let b = ((source.b() * source.a()) + (dest.b() * dest.a() * (1. - source.a()))) / a;
|
||||
|
||||
Self(ColorF::new(r, g, b, a).to_u8())
|
||||
}
|
||||
|
||||
pub fn fade_out(&mut self, factor: f32) {
|
||||
let source_alpha = 1. - factor.clamp(0., 1.);
|
||||
let dest_alpha = self.0.a as f32 / 255.;
|
||||
let dest_alpha = source_alpha + (dest_alpha * (1. - source_alpha));
|
||||
self.0.a = (dest_alpha * (1. / 255.)) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Color {
|
||||
|
|
|
@ -67,17 +67,20 @@ impl Element for Text {
|
|||
let mut highlight_ranges = self.highlights.iter().peekable();
|
||||
let chunks = std::iter::from_fn(|| {
|
||||
let result;
|
||||
if let Some((range, highlight)) = highlight_ranges.peek() {
|
||||
if let Some((range, highlight_style)) = highlight_ranges.peek() {
|
||||
if offset < range.start {
|
||||
result = Some((&self.text[offset..range.start], None));
|
||||
result = Some((
|
||||
&self.text[offset..range.start],
|
||||
HighlightStyle::from(&self.style),
|
||||
));
|
||||
offset = range.start;
|
||||
} else {
|
||||
result = Some((&self.text[range.clone()], Some(*highlight)));
|
||||
result = Some((&self.text[range.clone()], *highlight_style));
|
||||
highlight_ranges.next();
|
||||
offset = range.end;
|
||||
}
|
||||
} else if offset < self.text.len() {
|
||||
result = Some((&self.text[offset..], None));
|
||||
result = Some((&self.text[offset..], HighlightStyle::from(&self.style)));
|
||||
offset = self.text.len();
|
||||
} else {
|
||||
result = None;
|
||||
|
@ -197,24 +200,24 @@ impl Element for Text {
|
|||
|
||||
/// Perform text layout on a series of highlighted chunks of text.
|
||||
pub fn layout_highlighted_chunks<'a>(
|
||||
chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>,
|
||||
style: &'a TextStyle,
|
||||
chunks: impl Iterator<Item = (&'a str, HighlightStyle)>,
|
||||
text_style: &'a TextStyle,
|
||||
text_layout_cache: &'a TextLayoutCache,
|
||||
font_cache: &'a Arc<FontCache>,
|
||||
max_line_len: usize,
|
||||
max_line_count: usize,
|
||||
) -> Vec<Line> {
|
||||
let mut layouts = Vec::with_capacity(max_line_count);
|
||||
let mut prev_font_properties = style.font_properties.clone();
|
||||
let mut prev_font_id = style.font_id;
|
||||
let mut prev_font_properties = text_style.font_properties.clone();
|
||||
let mut prev_font_id = text_style.font_id;
|
||||
let mut line = String::new();
|
||||
let mut styles = Vec::new();
|
||||
let mut row = 0;
|
||||
let mut line_exceeded_max_len = false;
|
||||
for (chunk, highlight_style) in chunks.chain([("\n", None)]) {
|
||||
for (chunk, highlight_style) in chunks.chain([("\n", Default::default())]) {
|
||||
for (ix, mut line_chunk) in chunk.split('\n').enumerate() {
|
||||
if ix > 0 {
|
||||
layouts.push(text_layout_cache.layout_str(&line, style.font_size, &styles));
|
||||
layouts.push(text_layout_cache.layout_str(&line, text_style.font_size, &styles));
|
||||
line.clear();
|
||||
styles.clear();
|
||||
row += 1;
|
||||
|
@ -225,15 +228,13 @@ pub fn layout_highlighted_chunks<'a>(
|
|||
}
|
||||
|
||||
if !line_chunk.is_empty() && !line_exceeded_max_len {
|
||||
let highlight_style = highlight_style.unwrap_or(style.clone().into());
|
||||
|
||||
// Avoid a lookup if the font properties match the previous ones.
|
||||
let font_id = if highlight_style.font_properties == prev_font_properties {
|
||||
prev_font_id
|
||||
} else {
|
||||
font_cache
|
||||
.select_font(style.font_family_id, &highlight_style.font_properties)
|
||||
.unwrap_or(style.font_id)
|
||||
.select_font(text_style.font_family_id, &highlight_style.font_properties)
|
||||
.unwrap_or(text_style.font_id)
|
||||
};
|
||||
|
||||
if line.len() + line_chunk.len() > max_line_len {
|
||||
|
|
|
@ -31,13 +31,16 @@ pub struct TextStyle {
|
|||
pub underline: Option<Underline>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
pub struct HighlightStyle {
|
||||
pub color: Color,
|
||||
pub font_properties: Properties,
|
||||
pub underline: Option<Underline>,
|
||||
pub fade_out: Option<f32>,
|
||||
}
|
||||
|
||||
impl Eq for HighlightStyle {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Underline {
|
||||
pub color: Color,
|
||||
|
@ -83,6 +86,8 @@ struct HighlightStyleJson {
|
|||
italic: bool,
|
||||
#[serde(default)]
|
||||
underline: UnderlineStyleJson,
|
||||
#[serde(default)]
|
||||
fade_out: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -131,7 +136,10 @@ impl TextStyle {
|
|||
if self.font_properties != style.font_properties {
|
||||
self.font_id = font_cache.select_font(self.font_family_id, &style.font_properties)?;
|
||||
}
|
||||
self.color = style.color;
|
||||
self.color = Color::blend(self.color, style.color);
|
||||
if let Some(factor) = style.fade_out {
|
||||
self.color.fade_out(factor);
|
||||
}
|
||||
self.underline = style.underline;
|
||||
Ok(self)
|
||||
}
|
||||
|
@ -199,10 +207,17 @@ impl TextStyle {
|
|||
|
||||
impl From<TextStyle> for HighlightStyle {
|
||||
fn from(other: TextStyle) -> Self {
|
||||
Self::from(&other)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TextStyle> for HighlightStyle {
|
||||
fn from(other: &TextStyle) -> Self {
|
||||
Self {
|
||||
color: other.color,
|
||||
font_properties: other.font_properties,
|
||||
underline: other.underline,
|
||||
fade_out: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -246,6 +261,18 @@ impl HighlightStyle {
|
|||
color: json.color,
|
||||
font_properties,
|
||||
underline: underline_from_json(json.underline, json.color),
|
||||
fade_out: json.fade_out,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn highlight(&mut self, other: HighlightStyle) {
|
||||
self.color = Color::blend(other.color, self.color);
|
||||
if let Some(factor) = other.fade_out {
|
||||
self.color.fade_out(factor);
|
||||
}
|
||||
self.font_properties = other.font_properties;
|
||||
if other.underline.is_some() {
|
||||
self.underline = other.underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -256,6 +283,7 @@ impl From<Color> for HighlightStyle {
|
|||
color,
|
||||
font_properties: Default::default(),
|
||||
underline: None,
|
||||
fade_out: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,6 +323,7 @@ impl<'de> Deserialize<'de> for HighlightStyle {
|
|||
color: serde_json::from_value(json).map_err(de::Error::custom)?,
|
||||
font_properties: Properties::new(),
|
||||
underline: None,
|
||||
fade_out: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue