Make it possible to set attributes per glyph

This commit is contained in:
Jeremy Soller 2022-10-26 15:16:06 -06:00
parent 4798c7ee1a
commit ea64291abb
No known key found for this signature in database
GPG key ID: 87F211AF2BE4C2FE
4 changed files with 254 additions and 168 deletions

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use cosmic_text::{Attrs, AttrsSpan, Color, Family, FontSystem, Style, SwashCache, use cosmic_text::{Attrs, Color, Family, FontSystem, Style, SwashCache,
TextAction, TextBuffer, TextBufferLine, TextMetrics, Weight}; TextAction, TextBuffer, TextBufferLine, TextMetrics, Weight};
use orbclient::{EventOption, Renderer, Window, WindowFlag}; use orbclient::{EventOption, Renderer, Window, WindowFlag};
use std::{env, fs, process, thread, time::{Duration, Instant}}; use std::{env, fs, process, thread, time::{Duration, Instant}};
@ -39,6 +39,11 @@ fn main() {
let mut line_i = 0; let mut line_i = 0;
for &(text, attrs) in &[ for &(text, attrs) in &[
("B", attrs.weight(Weight::BOLD)),
("old ", attrs),
("I", attrs.style(Style::Italic)),
("talic", attrs),
("\n", attrs),
("Sans-Serif Normal ", attrs), ("Sans-Serif Normal ", attrs),
("Sans-Serif Bold ", attrs.weight(Weight::BOLD)), ("Sans-Serif Bold ", attrs.weight(Weight::BOLD)),
("Sans-Serif Italic ", attrs.style(Style::Italic)), ("Sans-Serif Italic ", attrs.style(Style::Italic)),
@ -73,6 +78,13 @@ fn main() {
("Blue ", attrs.color(Color::rgb(0x00, 0x00, 0xFF))), ("Blue ", attrs.color(Color::rgb(0x00, 0x00, 0xFF))),
("Indigo ", attrs.color(Color::rgb(0x4B, 0x00, 0x82))), ("Indigo ", attrs.color(Color::rgb(0x4B, 0x00, 0x82))),
("Violet ", attrs.color(Color::rgb(0x94, 0x00, 0xD3))), ("Violet ", attrs.color(Color::rgb(0x94, 0x00, 0xD3))),
("U", attrs.color(Color::rgb(0x94, 0x00, 0xD3))),
("N", attrs.color(Color::rgb(0x4B, 0x00, 0x82))),
("I", attrs.color(Color::rgb(0x00, 0x00, 0xFF))),
("C", attrs.color(Color::rgb(0x00, 0xFF, 0x00))),
("O", attrs.color(Color::rgb(0xFF, 0xFF, 0x00))),
("R", attrs.color(Color::rgb(0xFF, 0x7F, 0x00))),
("N", attrs.color(Color::rgb(0xFF, 0x00, 0x00))),
] { ] {
if text == "\n" { if text == "\n" {
line_i += 1; line_i += 1;
@ -86,11 +98,7 @@ fn main() {
let start = line.text.len(); let start = line.text.len();
line.text.push_str(text); line.text.push_str(text);
let end = line.text.len(); let end = line.text.len();
line.attrs_spans.push(AttrsSpan { line.attrs_list.add_span(start, end, attrs);
start,
end,
attrs,
});
line.reset(); line.reset();
} }

View file

@ -80,10 +80,52 @@ impl<'a> Attrs<'a> {
//TODO: smarter way of including emoji //TODO: smarter way of including emoji
(face.monospaced == self.monospaced || face.post_script_name.contains("Emoji")) (face.monospaced == self.monospaced || face.post_script_name.contains("Emoji"))
} }
pub fn compatible(&self, other: &Self) -> bool {
self.family == other.family
&& self.monospaced == other.monospaced
&& self.stretch == other.stretch
&& self.style == other.style
&& self.weight == other.weight
}
} }
pub struct AttrsSpan<'a> { pub struct AttrsList<'a> {
pub start: usize, defaults: Attrs<'a>,
pub end: usize, spans: Vec<(usize, usize, Attrs<'a>)>,
pub attrs: Attrs<'a> }
impl<'a> AttrsList<'a> {
pub fn new(defaults: Attrs<'a>) -> Self {
Self {
defaults,
spans: Vec::new(),
}
}
pub fn defaults(&self) -> Attrs<'a> {
self.defaults
}
pub fn spans(&self) -> &Vec<(usize, usize, Attrs<'a>)> {
&self.spans
}
pub fn clear_spans(&mut self) {
self.spans.clear();
}
pub fn add_span(&mut self, start: usize, end: usize, attrs: Attrs<'a>) {
self.spans.push((start, end, attrs));
}
pub fn get_span(&self, start: usize, end: usize) -> Attrs<'a> {
let mut attrs = self.defaults;
for span in self.spans.iter() {
if start >= span.0 && end <= span.1 {
attrs = span.2;
}
}
attrs
}
} }

View file

@ -7,7 +7,7 @@ use std::{
}; };
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use crate::{Attrs, AttrsSpan, FontSystem, LayoutGlyph, LayoutLine, ShapeLine}; use crate::{Attrs, AttrsList, FontSystem, LayoutGlyph, LayoutLine, ShapeLine};
/// An action to perform on a [TextBuffer] /// An action to perform on a [TextBuffer]
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
@ -175,21 +175,16 @@ impl fmt::Display for TextMetrics {
pub struct TextBufferLine<'a> { pub struct TextBufferLine<'a> {
pub text: String, pub text: String,
pub attrs_spans: Vec<AttrsSpan<'a>>, pub attrs_list: AttrsList<'a>,
shape_opt: Option<ShapeLine>, shape_opt: Option<ShapeLine>,
layout_opt: Option<Vec<LayoutLine>>, layout_opt: Option<Vec<LayoutLine>>,
} }
impl<'a> TextBufferLine<'a> { impl<'a> TextBufferLine<'a> {
pub fn new(text: String, attrs: Attrs<'a>) -> Self { pub fn new(text: String, attrs: Attrs<'a>) -> Self {
let attrs_spans = vec![AttrsSpan {
start: 0,
end: text.len(),
attrs
}];
Self { Self {
text, text,
attrs_spans, attrs_list: AttrsList::new(attrs),
shape_opt: None, shape_opt: None,
layout_opt: None, layout_opt: None,
} }
@ -206,7 +201,7 @@ impl<'a> TextBufferLine<'a> {
pub fn shape(&mut self, font_system: &'a FontSystem<'a>) -> &ShapeLine { pub fn shape(&mut self, font_system: &'a FontSystem<'a>) -> &ShapeLine {
if self.shape_opt.is_none() { if self.shape_opt.is_none() {
self.shape_opt = Some(ShapeLine::new(font_system, &self.text, &self.attrs_spans)); self.shape_opt = Some(ShapeLine::new(font_system, &self.text, &self.attrs_list));
self.layout_opt = None; self.layout_opt = None;
} }
self.shape_opt.as_ref().unwrap() self.shape_opt.as_ref().unwrap()
@ -509,12 +504,8 @@ impl<'a> TextBuffer<'a> {
self.attrs = attrs; self.attrs = attrs;
for line in self.lines.iter_mut() { for line in self.lines.iter_mut() {
line.attrs_list = AttrsList::new(attrs);
line.reset(); line.reset();
line.attrs_spans = vec![AttrsSpan {
start: 0,
end: line.text.len(),
attrs
}];
} }
self.shape_until_scroll(); self.shape_until_scroll();

View file

@ -1,19 +1,20 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use unicode_script::{Script, UnicodeScript}; use unicode_script::{Script, UnicodeScript};
use unicode_segmentation::UnicodeSegmentation;
use crate::{Attrs, AttrsSpan, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine}; use crate::{AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine};
use crate::fallback::FontFallbackIter; use crate::fallback::FontFallbackIter;
fn shape_fallback( fn shape_fallback(
font: &Font, font: &Font,
line: &str, line: &str,
attrs_spans: &[AttrsSpan], attrs_list: &AttrsList,
start_word: usize, start_run: usize,
end_word: usize, end_run: usize,
span_rtl: bool, span_rtl: bool,
) -> (Vec<ShapeGlyph>, Vec<usize>) { ) -> (Vec<ShapeGlyph>, Vec<usize>) {
let word = &line[start_word..end_word]; let run = &line[start_run..end_run];
let font_scale = font.rustybuzz.units_per_em() as f32; let font_scale = font.rustybuzz.units_per_em() as f32;
@ -23,7 +24,7 @@ fn shape_fallback(
} else { } else {
rustybuzz::Direction::LeftToRight rustybuzz::Direction::LeftToRight
}); });
buffer.push_str(word); buffer.push_str(run);
buffer.guess_segment_properties(); buffer.guess_segment_properties();
let rtl = match buffer.direction() { let rtl = match buffer.direction() {
@ -45,32 +46,23 @@ fn shape_fallback(
let x_offset = pos.x_offset as f32 / font_scale; let x_offset = pos.x_offset as f32 / font_scale;
let y_offset = pos.y_offset as f32 / font_scale; let y_offset = pos.y_offset as f32 / font_scale;
let start_glyph = start_word + info.cluster as usize; let start_glyph = start_run + info.cluster as usize;
//println!(" {:?} {:?}", info, pos); //println!(" {:?} {:?}", info, pos);
if info.glyph_id == 0 { if info.glyph_id == 0 {
missing.push(start_glyph); missing.push(start_glyph);
} }
let mut attrs = Attrs::new();
for attrs_span in attrs_spans.iter() {
//TODO: BETTER SUPPORT ATTRIBUTES PER GLYPH
if attrs_span.start <= start_glyph && attrs_span.end > start_glyph {
attrs = attrs_span.attrs;
break;
}
}
glyphs.push(ShapeGlyph { glyphs.push(ShapeGlyph {
start: start_glyph, start: start_glyph,
end: end_word, // Set later end: end_run, // Set later
x_advance, x_advance,
y_advance, y_advance,
x_offset, x_offset,
y_offset, y_offset,
font_id: font.info.id, font_id: font.info.id,
glyph_id: info.glyph_id.try_into().unwrap(), glyph_id: info.glyph_id.try_into().unwrap(),
color_opt: attrs.color_opt, color_opt: None,
}); });
} }
@ -99,65 +91,27 @@ fn shape_fallback(
} }
} }
// Set color
//TODO: these attributes should not be related to shaping
for glyph in glyphs.iter_mut() {
let attrs = attrs_list.get_span(glyph.start, glyph.end);
glyph.color_opt = attrs.color_opt;
}
(glyphs, missing) (glyphs, missing)
} }
pub struct ShapeGlyph { fn shape_run<'a>(
pub start: usize,
pub end: usize,
pub x_advance: f32,
pub y_advance: f32,
pub x_offset: f32,
pub y_offset: f32,
pub font_id: fontdb::ID,
pub glyph_id: u16,
pub color_opt: Option<Color>,
}
impl ShapeGlyph {
fn layout(&self, font_size: i32, x: f32, y: f32, rtl: bool) -> LayoutGlyph {
let x_offset = font_size as f32 * self.x_offset;
let y_offset = font_size as f32 * self.y_offset;
let x_advance = font_size as f32 * self.x_advance;
let (cache_key, x_int, y_int) = CacheKey::new(
self.font_id,
self.glyph_id,
font_size,
(x + x_offset, y - y_offset)
);
LayoutGlyph {
start: self.start,
end: self.end,
x,
w: x_advance,
rtl,
cache_key,
x_int,
y_int,
color_opt: self.color_opt,
}
}
}
pub struct ShapeWord {
pub blank: bool,
pub glyphs: Vec<ShapeGlyph>,
}
impl ShapeWord {
pub fn new<'a>(
font_system: &'a FontSystem<'a>, font_system: &'a FontSystem<'a>,
line: &str, line: &str,
attrs_spans: &[AttrsSpan<'a>], attrs_list: &AttrsList<'a>,
start_word: usize, start_run: usize,
end_word: usize, end_run: usize,
span_rtl: bool, span_rtl: bool,
blank: bool, ) -> Vec<ShapeGlyph> {
) -> Self {
//TODO: use smallvec? //TODO: use smallvec?
let mut scripts = Vec::new(); let mut scripts = Vec::new();
for c in line[start_word..end_word].chars() { for c in line[start_run..end_run].chars() {
match c.script() { match c.script() {
Script::Common | Script::Common |
Script::Inherited | Script::Inherited |
@ -170,20 +124,12 @@ impl ShapeWord {
} }
log::trace!( log::trace!(
" Word {:?}{}: '{}'", " Run {:?}: '{}'",
scripts, scripts,
if blank { " BLANK" } else { "" }, &line[start_run..end_run],
&line[start_word..end_word],
); );
let mut attrs = Attrs::new(); let attrs = attrs_list.get_span(start_run, end_run);
for attrs_span in attrs_spans.iter() {
//TODO: BETTER SUPPORT ATTRIBUTES PER GLYPH
if attrs_span.start <= start_word && attrs_span.end >= end_word {
attrs = attrs_span.attrs;
break;
}
}
let font_matches = font_system.get_font_matches(attrs); let font_matches = font_system.get_font_matches(attrs);
@ -198,9 +144,9 @@ impl ShapeWord {
let (mut glyphs, mut missing) = shape_fallback( let (mut glyphs, mut missing) = shape_fallback(
font_iter.next().unwrap(), font_iter.next().unwrap(),
line, line,
attrs_spans, attrs_list,
start_word, start_run,
end_word, end_run,
span_rtl, span_rtl,
); );
@ -215,9 +161,9 @@ impl ShapeWord {
let (mut fb_glyphs, fb_missing) = shape_fallback( let (mut fb_glyphs, fb_missing) = shape_fallback(
font, font,
line, line,
attrs_spans, attrs_list,
start_word, start_run,
end_word, end_run,
span_rtl, span_rtl,
); );
@ -277,7 +223,7 @@ impl ShapeWord {
} }
// Debug missing font fallbacks // Debug missing font fallbacks
font_iter.check_missing(&line[start_word..end_word]); font_iter.check_missing(&line[start_run..end_run]);
/* /*
for glyph in glyphs.iter() { for glyph in glyphs.iter() {
@ -285,6 +231,105 @@ impl ShapeWord {
} }
*/ */
glyphs
}
pub struct ShapeGlyph {
pub start: usize,
pub end: usize,
pub x_advance: f32,
pub y_advance: f32,
pub x_offset: f32,
pub y_offset: f32,
pub font_id: fontdb::ID,
pub glyph_id: u16,
pub color_opt: Option<Color>,
}
impl ShapeGlyph {
fn layout(&self, font_size: i32, x: f32, y: f32, rtl: bool) -> LayoutGlyph {
let x_offset = font_size as f32 * self.x_offset;
let y_offset = font_size as f32 * self.y_offset;
let x_advance = font_size as f32 * self.x_advance;
let (cache_key, x_int, y_int) = CacheKey::new(
self.font_id,
self.glyph_id,
font_size,
(x + x_offset, y - y_offset)
);
LayoutGlyph {
start: self.start,
end: self.end,
x,
w: x_advance,
rtl,
cache_key,
x_int,
y_int,
color_opt: self.color_opt,
}
}
}
pub struct ShapeWord {
pub blank: bool,
pub glyphs: Vec<ShapeGlyph>,
}
impl ShapeWord {
pub fn new<'a>(
font_system: &'a FontSystem<'a>,
line: &str,
attrs_list: &AttrsList<'a>,
start_word: usize,
end_word: usize,
span_rtl: bool,
blank: bool,
) -> Self {
let word = &line[start_word..end_word];
log::trace!(
" Word{}: '{}'",
if blank { " BLANK" } else { "" },
word
);
let mut glyphs = Vec::new();
let mut start_run = start_word;
let mut attrs = attrs_list.defaults();
for (egc_i, egc) in word.grapheme_indices(true) {
let start_egc = start_word + egc_i;
let end_egc = start_egc + egc.len();
let attrs_egc = attrs_list.get_span(start_egc, end_egc);
if ! attrs.compatible(&attrs_egc) {
//TODO: more efficient
glyphs.append(&mut shape_run(
font_system,
line,
attrs_list,
start_run,
start_egc,
span_rtl
));
start_run = start_egc;
attrs = attrs_egc;
}
}
if start_run < end_word {
//TODO: more efficient
glyphs.append(&mut shape_run(
font_system,
line,
attrs_list,
start_run,
end_word,
span_rtl
));
}
Self { blank, glyphs } Self { blank, glyphs }
} }
} }
@ -298,7 +343,7 @@ impl ShapeSpan {
pub fn new<'a>( pub fn new<'a>(
font_system: &'a FontSystem<'a>, font_system: &'a FontSystem<'a>,
line: &str, line: &str,
attrs_spans: &[AttrsSpan<'a>], attrs_list: &AttrsList<'a>,
start_span: usize, start_span: usize,
end_span: usize, end_span: usize,
line_rtl: bool, line_rtl: bool,
@ -328,7 +373,7 @@ impl ShapeSpan {
words.push(ShapeWord::new( words.push(ShapeWord::new(
font_system, font_system,
line, line,
attrs_spans, attrs_list,
start_span + start_word, start_span + start_word,
start_span + start_lb, start_span + start_lb,
span_rtl, span_rtl,
@ -339,7 +384,7 @@ impl ShapeSpan {
words.push(ShapeWord::new( words.push(ShapeWord::new(
font_system, font_system,
line, line,
attrs_spans, attrs_list,
start_span + start_lb, start_span + start_lb,
start_span + end_lb, start_span + end_lb,
span_rtl, span_rtl,
@ -377,7 +422,7 @@ impl ShapeLine {
pub fn new<'a>( pub fn new<'a>(
font_system: &'a FontSystem<'a>, font_system: &'a FontSystem<'a>,
line: &str, line: &str,
attrs_spans: &[AttrsSpan<'a>] attrs_list: &AttrsList<'a>
) -> Self { ) -> Self {
let mut spans = Vec::new(); let mut spans = Vec::new();
@ -401,7 +446,7 @@ impl ShapeLine {
spans.push(ShapeSpan::new( spans.push(ShapeSpan::new(
font_system, font_system,
line, line,
attrs_spans, attrs_list,
start, start,
i, i,
line_rtl, line_rtl,
@ -414,7 +459,7 @@ impl ShapeLine {
spans.push(ShapeSpan::new( spans.push(ShapeSpan::new(
font_system, font_system,
line, line,
attrs_spans, attrs_list,
start, start,
line.len(), line.len(),
line_rtl, line_rtl,