Add function to get layout runs
This commit is contained in:
parent
5603e30a29
commit
330a736136
6 changed files with 254 additions and 238 deletions
|
|
@ -25,6 +25,7 @@ use cosmic::{
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
FontMatches,
|
FontMatches,
|
||||||
FontSystem,
|
FontSystem,
|
||||||
|
SwashCache,
|
||||||
TextBuffer,
|
TextBuffer,
|
||||||
TextMetrics,
|
TextMetrics,
|
||||||
};
|
};
|
||||||
|
|
@ -63,6 +64,7 @@ pub struct Window {
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
path_opt: Option<PathBuf>,
|
path_opt: Option<PathBuf>,
|
||||||
buffer: Mutex<TextBuffer<'static>>,
|
buffer: Mutex<TextBuffer<'static>>,
|
||||||
|
cache: Mutex<SwashCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
@ -139,6 +141,7 @@ impl Application for Window {
|
||||||
theme: Theme::Dark,
|
theme: Theme::Dark,
|
||||||
path_opt: None,
|
path_opt: None,
|
||||||
buffer: Mutex::new(buffer),
|
buffer: Mutex::new(buffer),
|
||||||
|
cache: Mutex::new(SwashCache::new()),
|
||||||
};
|
};
|
||||||
if let Some(arg) = env::args().nth(1) {
|
if let Some(arg) = env::args().nth(1) {
|
||||||
window.open(PathBuf::from(arg));
|
window.open(PathBuf::from(arg));
|
||||||
|
|
@ -230,7 +233,7 @@ impl Application for Window {
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
,
|
,
|
||||||
text_box(&self.buffer)
|
text_box(&self.buffer, &self.cache)
|
||||||
]
|
]
|
||||||
.spacing(8)
|
.spacing(8)
|
||||||
.padding(16)
|
.padding(16)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ use cosmic::iced_native::{
|
||||||
widget::{self, tree, Widget},
|
widget::{self, tree, Widget},
|
||||||
};
|
};
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
|
SwashCache,
|
||||||
TextAction,
|
TextAction,
|
||||||
TextBuffer,
|
TextBuffer,
|
||||||
};
|
};
|
||||||
|
|
@ -62,16 +63,17 @@ impl StyleSheet for Theme {
|
||||||
|
|
||||||
pub struct TextBox<'a> {
|
pub struct TextBox<'a> {
|
||||||
buffer: &'a Mutex<TextBuffer<'static>>,
|
buffer: &'a Mutex<TextBuffer<'static>>,
|
||||||
|
cache: &'a Mutex<SwashCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TextBox<'a> {
|
impl<'a> TextBox<'a> {
|
||||||
pub fn new(buffer: &'a Mutex<TextBuffer<'static>>) -> Self {
|
pub fn new(buffer: &'a Mutex<TextBuffer<'static>>, cache: &'a Mutex<SwashCache>) -> Self {
|
||||||
Self { buffer }
|
Self { buffer, cache }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_box<'a>(buffer: &'a Mutex<TextBuffer<'static>>) -> TextBox<'a> {
|
pub fn text_box<'a>(buffer: &'a Mutex<TextBuffer<'static>>, cache: &'a Mutex<SwashCache>) -> TextBox<'a> {
|
||||||
TextBox::new(buffer)
|
TextBox::new(buffer, cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for TextBox<'a>
|
impl<'a, Message, Renderer> Widget<Message, Renderer> for TextBox<'a>
|
||||||
|
|
@ -153,12 +155,13 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut buffer = self.buffer.lock().unwrap();
|
let mut buffer = self.buffer.lock().unwrap();
|
||||||
|
let mut cache = self.cache.lock().unwrap();
|
||||||
|
|
||||||
buffer.shape_until_cursor();
|
buffer.shape_until_cursor();
|
||||||
|
|
||||||
let buffer_x = layout.bounds().x;
|
let buffer_x = layout.bounds().x;
|
||||||
let buffer_y = layout.bounds().y;
|
let buffer_y = layout.bounds().y;
|
||||||
buffer.draw(text_color_u32, |x, y, w, h, color| {
|
buffer.draw(&mut cache, text_color_u32, |x, y, w, h, color| {
|
||||||
let a = (color >> 24) as u8;
|
let a = (color >> 24) as u8;
|
||||||
if a > 0 {
|
if a > 0 {
|
||||||
let r = (color >> 16) as u8;
|
let r = (color >> 16) as u8;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use cosmic_text::{FontSystem, TextAction, TextBuffer, TextMetrics};
|
use cosmic_text::{FontSystem, SwashCache, TextAction, TextBuffer, TextMetrics};
|
||||||
use orbclient::{Color, EventOption, Renderer, Window, WindowFlag};
|
use orbclient::{Color, EventOption, Renderer, Window, WindowFlag};
|
||||||
use std::{env, fs, thread, time::{Duration, Instant}};
|
use std::{env, fs, thread, time::{Duration, Instant}};
|
||||||
|
|
||||||
|
|
@ -83,6 +83,8 @@ fn main() {
|
||||||
default_text.to_string()
|
default_text.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut swash_cache = SwashCache::new();
|
||||||
|
|
||||||
let line_x = 8 * display_scale;
|
let line_x = 8 * display_scale;
|
||||||
let mut buffer = TextBuffer::new(
|
let mut buffer = TextBuffer::new(
|
||||||
font_matches,
|
font_matches,
|
||||||
|
|
@ -110,7 +112,7 @@ fn main() {
|
||||||
|
|
||||||
window.set(bg_color);
|
window.set(bg_color);
|
||||||
|
|
||||||
buffer.draw(font_color.data, |x, y, w, h, color| {
|
buffer.draw(&mut swash_cache, font_color.data, |x, y, w, h, color| {
|
||||||
window.rect(line_x + x, y, w, h, Color { data: color });
|
window.rect(line_x + x, y, w, h, Color { data: color });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use cosmic_text::{FontSystem, TextAction, TextBuffer, TextMetrics};
|
use cosmic_text::{FontSystem, SwashCache, TextAction, TextBuffer, TextMetrics};
|
||||||
use orbclient::{Color, EventOption, Renderer, Window, WindowFlag};
|
use orbclient::{Color, EventOption, Renderer, Window, WindowFlag};
|
||||||
use std::{env, fs, process, thread, time::{Duration, Instant}};
|
use std::{env, fs, process, thread, time::{Duration, Instant}};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
fn redraw(window: &mut Window, buffer: &mut TextBuffer<'_>) {
|
fn redraw(window: &mut Window, buffer: &mut TextBuffer<'_>, swash_cache: &mut SwashCache) {
|
||||||
let bg_color = Color::rgb(0x34, 0x34, 0x34);
|
let bg_color = Color::rgb(0x34, 0x34, 0x34);
|
||||||
let font_color = Color::rgb(0xFF, 0xFF, 0xFF);
|
let font_color = Color::rgb(0xFF, 0xFF, 0xFF);
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ fn redraw(window: &mut Window, buffer: &mut TextBuffer<'_>) {
|
||||||
|
|
||||||
window.set(bg_color);
|
window.set(bg_color);
|
||||||
|
|
||||||
buffer.draw(font_color.data, |x, y, w, h, color| {
|
buffer.draw(swash_cache, font_color.data, |x, y, w, h, color| {
|
||||||
window.rect(x, y, w, h, Color { data: color });
|
window.rect(x, y, w, h, Color { data: color });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -94,6 +94,8 @@ fn main() {
|
||||||
window.height() as i32
|
window.height() as i32
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let mut swash_cache = SwashCache::new();
|
||||||
|
|
||||||
let text = if let Some(arg) = env::args().nth(1) {
|
let text = if let Some(arg) = env::args().nth(1) {
|
||||||
fs::read_to_string(&arg).expect("failed to open file")
|
fs::read_to_string(&arg).expect("failed to open file")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -158,7 +160,7 @@ fn main() {
|
||||||
// Finally, normal enter
|
// Finally, normal enter
|
||||||
buffer.action(TextAction::Enter);
|
buffer.action(TextAction::Enter);
|
||||||
|
|
||||||
redraw(&mut window, &mut buffer);
|
redraw(&mut window, &mut buffer, &mut swash_cache);
|
||||||
|
|
||||||
for event in window.events() {
|
for event in window.events() {
|
||||||
match event.to_option() {
|
match event.to_option() {
|
||||||
|
|
|
||||||
383
src/buffer.rs
383
src/buffer.rs
|
|
@ -63,18 +63,26 @@ impl TextCursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LayoutCursor {
|
struct TextLayoutCursor {
|
||||||
line: TextLineIndex,
|
line: TextLineIndex,
|
||||||
layout: usize,
|
layout: usize,
|
||||||
glyph: usize,
|
glyph: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutCursor {
|
impl TextLayoutCursor {
|
||||||
fn new(line: TextLineIndex, layout: usize, glyph: usize) -> Self {
|
fn new(line: TextLineIndex, layout: usize, glyph: usize) -> Self {
|
||||||
Self { line, layout, glyph }
|
Self { line, layout, glyph }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TextLayoutRun<'a> {
|
||||||
|
line_i: TextLineIndex,
|
||||||
|
text: &'a str,
|
||||||
|
shape: &'a FontShapeLine,
|
||||||
|
layout_line: &'a FontLayoutLine,
|
||||||
|
line_y: i32,
|
||||||
|
}
|
||||||
|
|
||||||
/// Index of a text line
|
/// Index of a text line
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub struct TextLineIndex(usize);
|
pub struct TextLineIndex(usize);
|
||||||
|
|
@ -176,8 +184,6 @@ pub struct TextBuffer<'a> {
|
||||||
cursor: TextCursor,
|
cursor: TextCursor,
|
||||||
select_opt: Option<TextCursor>,
|
select_opt: Option<TextCursor>,
|
||||||
pub redraw: bool,
|
pub redraw: bool,
|
||||||
#[cfg(feature = "swash")]
|
|
||||||
cache: crate::SwashCache,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TextBuffer<'a> {
|
impl<'a> TextBuffer<'a> {
|
||||||
|
|
@ -195,8 +201,6 @@ impl<'a> TextBuffer<'a> {
|
||||||
cursor: TextCursor::default(),
|
cursor: TextCursor::default(),
|
||||||
select_opt: None,
|
select_opt: None,
|
||||||
redraw: false,
|
redraw: false,
|
||||||
#[cfg(feature = "swash")]
|
|
||||||
cache: crate::SwashCache::new(),
|
|
||||||
};
|
};
|
||||||
buffer.set_text("");
|
buffer.set_text("");
|
||||||
buffer
|
buffer
|
||||||
|
|
@ -323,14 +327,14 @@ impl<'a> TextBuffer<'a> {
|
||||||
log::debug!("relayout: {:?}", duration);
|
log::debug!("relayout: {:?}", duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_cursor(&self, cursor: &TextCursor) -> LayoutCursor {
|
fn layout_cursor(&self, cursor: &TextCursor) -> TextLayoutCursor {
|
||||||
let line = &self.lines[cursor.line.get()];
|
let line = &self.lines[cursor.line.get()];
|
||||||
|
|
||||||
let layout = line.layout_opt.as_ref().unwrap(); //TODO: ensure layout is done?
|
let layout = line.layout_opt.as_ref().unwrap(); //TODO: ensure layout is done?
|
||||||
for (layout_i, layout_line) in layout.iter().enumerate() {
|
for (layout_i, layout_line) in layout.iter().enumerate() {
|
||||||
for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
|
for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
|
||||||
if cursor.index == glyph.start {
|
if cursor.index == glyph.start {
|
||||||
return LayoutCursor::new(
|
return TextLayoutCursor::new(
|
||||||
cursor.line,
|
cursor.line,
|
||||||
layout_i,
|
layout_i,
|
||||||
glyph_i
|
glyph_i
|
||||||
|
|
@ -340,7 +344,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
match layout_line.glyphs.last() {
|
match layout_line.glyphs.last() {
|
||||||
Some(glyph) => {
|
Some(glyph) => {
|
||||||
if cursor.index == glyph.end {
|
if cursor.index == glyph.end {
|
||||||
return LayoutCursor::new(
|
return TextLayoutCursor::new(
|
||||||
cursor.line,
|
cursor.line,
|
||||||
layout_i,
|
layout_i,
|
||||||
layout_line.glyphs.len()
|
layout_line.glyphs.len()
|
||||||
|
|
@ -348,7 +352,7 @@ impl<'a> TextBuffer<'a> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
return LayoutCursor::new(
|
return TextLayoutCursor::new(
|
||||||
cursor.line,
|
cursor.line,
|
||||||
layout_i,
|
layout_i,
|
||||||
0
|
0
|
||||||
|
|
@ -359,14 +363,14 @@ impl<'a> TextBuffer<'a> {
|
||||||
|
|
||||||
// Fall back to start of line
|
// Fall back to start of line
|
||||||
//TODO: should this be the end of the line?
|
//TODO: should this be the end of the line?
|
||||||
LayoutCursor::new(
|
TextLayoutCursor::new(
|
||||||
cursor.line,
|
cursor.line,
|
||||||
0,
|
0,
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_layout_cursor(&mut self, cursor: LayoutCursor) {
|
fn set_layout_cursor(&mut self, cursor: TextLayoutCursor) {
|
||||||
let line = &mut self.lines[cursor.line.get()];
|
let line = &mut self.lines[cursor.line.get()];
|
||||||
let layout = line.layout(
|
let layout = line.layout(
|
||||||
&self.font_matches,
|
&self.font_matches,
|
||||||
|
|
@ -797,22 +801,10 @@ impl<'a> TextBuffer<'a> {
|
||||||
log::trace!("click({}, {}): {:?}", mouse_x, mouse_y, duration);
|
log::trace!("click({}, {}): {:?}", mouse_x, mouse_y, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the buffer
|
/// Get the visible layout runs for rendering and other tasks
|
||||||
#[cfg(feature = "swash")]
|
pub fn with_layout_runs<F: FnMut(TextLayoutRun)>(&self, mut f: F) {
|
||||||
pub fn draw<F>(&mut self, color: u32, mut f: F)
|
let mut line_y = self.metrics.font_size;
|
||||||
where F: FnMut(i32, i32, u32, u32, u32)
|
let mut total_layout = 0;
|
||||||
{
|
|
||||||
let font_size = self.metrics.font_size;
|
|
||||||
let line_height = self.metrics.line_height;
|
|
||||||
/*TODO
|
|
||||||
let layout_cursor = self.layout_cursor(&self.cursor);
|
|
||||||
let layout_select_opt = self.select_opt.as_ref().map(|cursor| {
|
|
||||||
self.layout_cursor(cursor)
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
let mut line_y = font_size;
|
|
||||||
let mut layout_i = 0;
|
|
||||||
for (line_i, line) in self.lines.iter().enumerate() {
|
for (line_i, line) in self.lines.iter().enumerate() {
|
||||||
let shape = match line.shape_opt.as_ref() {
|
let shape = match line.shape_opt.as_ref() {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
|
|
@ -825,8 +817,8 @@ impl<'a> TextBuffer<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
for layout_line in layout {
|
for layout_line in layout {
|
||||||
let scrolled = layout_i < self.scroll;
|
let scrolled = total_layout < self.scroll;
|
||||||
layout_i += 1;
|
total_layout += 1;
|
||||||
|
|
||||||
if scrolled {
|
if scrolled {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -836,162 +828,185 @@ impl<'a> TextBuffer<'a> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cursor_glyph_opt = |cursor: &TextCursor| -> Option<(usize, f32)> {
|
f(TextLayoutRun {
|
||||||
if cursor.line.get() == line_i {
|
line_i: TextLineIndex::new(line_i),
|
||||||
for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
|
text: line.text.as_str(),
|
||||||
if cursor.index == glyph.start {
|
shape,
|
||||||
return Some((glyph_i, 0.0));
|
layout_line,
|
||||||
} else if cursor.index > glyph.start && cursor.index < glyph.end {
|
line_y,
|
||||||
// Guess x offset based on characters
|
});
|
||||||
let mut before = 0;
|
|
||||||
let mut total = 0;
|
|
||||||
|
|
||||||
let cluster = &line.text[glyph.start..glyph.end];
|
line_y += self.metrics.line_height;
|
||||||
for (i, _) in cluster.grapheme_indices(true) {
|
|
||||||
if glyph.start + i < cursor.index {
|
|
||||||
before += 1;
|
|
||||||
}
|
|
||||||
total += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = glyph.w * (before as f32) / (total as f32);
|
|
||||||
return Some((glyph_i, offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match layout_line.glyphs.last() {
|
|
||||||
Some(glyph) => {
|
|
||||||
if cursor.index == glyph.end {
|
|
||||||
return Some((layout_line.glyphs.len(), 0.0));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
return Some((0, 0.0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// Highlight selection (TODO: HIGHLIGHT COLOR!)
|
|
||||||
if let Some(select) = self.select_opt {
|
|
||||||
let (start, end) = match select.line.cmp(&self.cursor.line) {
|
|
||||||
cmp::Ordering::Greater => (self.cursor, select),
|
|
||||||
cmp::Ordering::Less => (select, self.cursor),
|
|
||||||
cmp::Ordering::Equal => {
|
|
||||||
/* select.line == self.cursor.line */
|
|
||||||
if select.index < self.cursor.index {
|
|
||||||
(select, self.cursor)
|
|
||||||
} else {
|
|
||||||
/* select.index >= self.cursor.index */
|
|
||||||
(self.cursor, select)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if line_i >= start.line.get() && line_i <= end.line.get() {
|
|
||||||
let mut range_opt = None;
|
|
||||||
for glyph in layout_line.glyphs.iter() {
|
|
||||||
// Guess x offset based on characters
|
|
||||||
let cluster = &line.text[glyph.start..glyph.end];
|
|
||||||
let total = cluster.grapheme_indices(true).count();
|
|
||||||
let mut c_x = glyph.x;
|
|
||||||
let c_w = glyph.w / total as f32;
|
|
||||||
for (i, c) in cluster.grapheme_indices(true) {
|
|
||||||
let c_start = glyph.start + i;
|
|
||||||
let c_end = glyph.start + i + c.len();
|
|
||||||
if (start.line.get() != line_i || c_end > start.index)
|
|
||||||
&& (end.line.get() != line_i || c_start < end.index) {
|
|
||||||
range_opt = match range_opt.take() {
|
|
||||||
Some((min, max)) => Some((
|
|
||||||
cmp::min(min, c_x as i32),
|
|
||||||
cmp::max(max, (c_x + c_w) as i32),
|
|
||||||
)),
|
|
||||||
None => Some((
|
|
||||||
c_x as i32,
|
|
||||||
(c_x + c_w) as i32,
|
|
||||||
))
|
|
||||||
};
|
|
||||||
} else if let Some((min, max)) = range_opt.take() {
|
|
||||||
f(
|
|
||||||
min,
|
|
||||||
line_y - font_size,
|
|
||||||
cmp::max(0, max - min) as u32,
|
|
||||||
line_height as u32,
|
|
||||||
0x33_00_00_00 | (color & 0xFF_FF_FF)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
c_x += c_w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((mut min, mut max)) = range_opt.take() {
|
|
||||||
if end.line.get() > line_i {
|
|
||||||
// Draw to end of line
|
|
||||||
if shape.rtl {
|
|
||||||
min = 0;
|
|
||||||
} else {
|
|
||||||
max = self.width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f(
|
|
||||||
min,
|
|
||||||
line_y - font_size,
|
|
||||||
cmp::max(0, max - min) as u32,
|
|
||||||
line_height as u32,
|
|
||||||
0x33_00_00_00 | (color & 0xFF_FF_FF)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw cursor
|
|
||||||
//TODO: draw at end of line but not start of next line
|
|
||||||
if let Some((cursor_glyph, cursor_glyph_offset)) = cursor_glyph_opt(&self.cursor) {
|
|
||||||
let x = match layout_line.glyphs.get(cursor_glyph) {
|
|
||||||
Some(glyph) => {
|
|
||||||
// Start of detected glyph
|
|
||||||
if glyph.rtl {
|
|
||||||
(glyph.x + glyph.w - cursor_glyph_offset) as i32
|
|
||||||
} else {
|
|
||||||
(glyph.x + cursor_glyph_offset) as i32
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => match layout_line.glyphs.last() {
|
|
||||||
Some(glyph) => {
|
|
||||||
// End of last glyph
|
|
||||||
if glyph.rtl {
|
|
||||||
glyph.x as i32
|
|
||||||
} else {
|
|
||||||
(glyph.x + glyph.w) as i32
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
// Start of empty line
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
f(
|
|
||||||
x,
|
|
||||||
line_y - font_size,
|
|
||||||
1,
|
|
||||||
line_height as u32,
|
|
||||||
color,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for glyph in layout_line.glyphs.iter() {
|
|
||||||
let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int);
|
|
||||||
self.cache.with_pixels(&self.font_matches, cache_key, color, |x, y, color| {
|
|
||||||
f(x_int + x, line_y + y_int + y, 1, 1, color)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
line_y += line_height;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw the buffer
|
||||||
|
#[cfg(feature = "swash")]
|
||||||
|
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: u32, mut f: F)
|
||||||
|
where F: FnMut(i32, i32, u32, u32, u32)
|
||||||
|
{
|
||||||
|
let font_size = self.metrics.font_size;
|
||||||
|
let line_height = self.metrics.line_height;
|
||||||
|
|
||||||
|
self.with_layout_runs(|run| {
|
||||||
|
let line_i = run.line_i;
|
||||||
|
let text = run.text;
|
||||||
|
let shape = run.shape;
|
||||||
|
let layout_line = run.layout_line;
|
||||||
|
let line_y = run.line_y;
|
||||||
|
|
||||||
|
let cursor_glyph_opt = |cursor: &TextCursor| -> Option<(usize, f32)> {
|
||||||
|
if cursor.line == line_i {
|
||||||
|
for (glyph_i, glyph) in layout_line.glyphs.iter().enumerate() {
|
||||||
|
if cursor.index == glyph.start {
|
||||||
|
return Some((glyph_i, 0.0));
|
||||||
|
} else if cursor.index > glyph.start && cursor.index < glyph.end {
|
||||||
|
// Guess x offset based on characters
|
||||||
|
let mut before = 0;
|
||||||
|
let mut total = 0;
|
||||||
|
|
||||||
|
let cluster = &text[glyph.start..glyph.end];
|
||||||
|
for (i, _) in cluster.grapheme_indices(true) {
|
||||||
|
if glyph.start + i < cursor.index {
|
||||||
|
before += 1;
|
||||||
|
}
|
||||||
|
total += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = glyph.w * (before as f32) / (total as f32);
|
||||||
|
return Some((glyph_i, offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match layout_line.glyphs.last() {
|
||||||
|
Some(glyph) => {
|
||||||
|
if cursor.index == glyph.end {
|
||||||
|
return Some((layout_line.glyphs.len(), 0.0));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
return Some((0, 0.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Highlight selection (TODO: HIGHLIGHT COLOR!)
|
||||||
|
if let Some(select) = self.select_opt {
|
||||||
|
let (start, end) = match select.line.cmp(&self.cursor.line) {
|
||||||
|
cmp::Ordering::Greater => (self.cursor, select),
|
||||||
|
cmp::Ordering::Less => (select, self.cursor),
|
||||||
|
cmp::Ordering::Equal => {
|
||||||
|
/* select.line == self.cursor.line */
|
||||||
|
if select.index < self.cursor.index {
|
||||||
|
(select, self.cursor)
|
||||||
|
} else {
|
||||||
|
/* select.index >= self.cursor.index */
|
||||||
|
(self.cursor, select)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if line_i >= start.line && line_i <= end.line {
|
||||||
|
let mut range_opt = None;
|
||||||
|
for glyph in layout_line.glyphs.iter() {
|
||||||
|
// Guess x offset based on characters
|
||||||
|
let cluster = &text[glyph.start..glyph.end];
|
||||||
|
let total = cluster.grapheme_indices(true).count();
|
||||||
|
let mut c_x = glyph.x;
|
||||||
|
let c_w = glyph.w / total as f32;
|
||||||
|
for (i, c) in cluster.grapheme_indices(true) {
|
||||||
|
let c_start = glyph.start + i;
|
||||||
|
let c_end = glyph.start + i + c.len();
|
||||||
|
if (start.line != line_i || c_end > start.index)
|
||||||
|
&& (end.line != line_i || c_start < end.index) {
|
||||||
|
range_opt = match range_opt.take() {
|
||||||
|
Some((min, max)) => Some((
|
||||||
|
cmp::min(min, c_x as i32),
|
||||||
|
cmp::max(max, (c_x + c_w) as i32),
|
||||||
|
)),
|
||||||
|
None => Some((
|
||||||
|
c_x as i32,
|
||||||
|
(c_x + c_w) as i32,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
} else if let Some((min, max)) = range_opt.take() {
|
||||||
|
f(
|
||||||
|
min,
|
||||||
|
line_y - font_size,
|
||||||
|
cmp::max(0, max - min) as u32,
|
||||||
|
line_height as u32,
|
||||||
|
0x33_00_00_00 | (color & 0xFF_FF_FF)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
c_x += c_w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((mut min, mut max)) = range_opt.take() {
|
||||||
|
if end.line > line_i {
|
||||||
|
// Draw to end of line
|
||||||
|
if shape.rtl {
|
||||||
|
min = 0;
|
||||||
|
} else {
|
||||||
|
max = self.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f(
|
||||||
|
min,
|
||||||
|
line_y - font_size,
|
||||||
|
cmp::max(0, max - min) as u32,
|
||||||
|
line_height as u32,
|
||||||
|
0x33_00_00_00 | (color & 0xFF_FF_FF)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw cursor
|
||||||
|
//TODO: draw at end of line but not start of next line
|
||||||
|
if let Some((cursor_glyph, cursor_glyph_offset)) = cursor_glyph_opt(&self.cursor) {
|
||||||
|
let x = match layout_line.glyphs.get(cursor_glyph) {
|
||||||
|
Some(glyph) => {
|
||||||
|
// Start of detected glyph
|
||||||
|
if glyph.rtl {
|
||||||
|
(glyph.x + glyph.w - cursor_glyph_offset) as i32
|
||||||
|
} else {
|
||||||
|
(glyph.x + cursor_glyph_offset) as i32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => match layout_line.glyphs.last() {
|
||||||
|
Some(glyph) => {
|
||||||
|
// End of last glyph
|
||||||
|
if glyph.rtl {
|
||||||
|
glyph.x as i32
|
||||||
|
} else {
|
||||||
|
(glyph.x + glyph.w) as i32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
// Start of empty line
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
f(
|
||||||
|
x,
|
||||||
|
line_y - font_size,
|
||||||
|
1,
|
||||||
|
line_height as u32,
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for glyph in layout_line.glyphs.iter() {
|
||||||
|
let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int);
|
||||||
|
cache.with_pixels(&self.font_matches, cache_key, color, |x, y, color| {
|
||||||
|
f(x_int + x, line_y + y_int + y, 1, 1, color)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
75
src/swash.rs
75
src/swash.rs
|
|
@ -22,13 +22,8 @@ impl SwashCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a swash Image from a cache key, caching results
|
/// Create a swash Image from a cache key, caching results
|
||||||
pub fn with_image<F: FnMut(&Option<SwashImage>)>(
|
pub fn get_image(&mut self, matches: &FontMatches<'_>, cache_key: CacheKey) -> &Option<SwashImage> {
|
||||||
&mut self,
|
self.cache.entry(cache_key).or_insert_with(|| {
|
||||||
matches: &FontMatches<'_>,
|
|
||||||
cache_key: CacheKey,
|
|
||||||
mut f: F
|
|
||||||
) {
|
|
||||||
let image_opt = self.cache.entry(cache_key).or_insert_with(|| {
|
|
||||||
let font = match matches.get_font(&cache_key.font_id) {
|
let font = match matches.get_font(&cache_key.font_id) {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -64,9 +59,7 @@ impl SwashCache {
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
// Render the image
|
// Render the image
|
||||||
.render(&mut scaler, cache_key.glyph_id)
|
.render(&mut scaler, cache_key.glyph_id)
|
||||||
});
|
})
|
||||||
|
|
||||||
f(image_opt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enumerate pixels in an Image, use `with_image` for better performance
|
/// Enumerate pixels in an Image, use `with_image` for better performance
|
||||||
|
|
@ -77,42 +70,40 @@ impl SwashCache {
|
||||||
base: u32,
|
base: u32,
|
||||||
mut f: F
|
mut f: F
|
||||||
) {
|
) {
|
||||||
self.with_image(matches, cache_key, |image_opt| {
|
if let Some(image) = self.get_image(matches, cache_key) {
|
||||||
if let Some(image) = image_opt {
|
let x = image.placement.left;
|
||||||
let x = image.placement.left;
|
let y = -image.placement.top;
|
||||||
let y = -image.placement.top;
|
|
||||||
|
|
||||||
match image.content {
|
match image.content {
|
||||||
Content::Mask => {
|
Content::Mask => {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
for off_y in 0..image.placement.height as i32 {
|
for off_y in 0..image.placement.height as i32 {
|
||||||
for off_x in 0..image.placement.width as i32 {
|
for off_x in 0..image.placement.width as i32 {
|
||||||
//TODO: blend base alpha?
|
//TODO: blend base alpha?
|
||||||
let color = (image.data[i] as u32) << 24 | base & 0xFFFFFF;
|
let color = (image.data[i] as u32) << 24 | base & 0xFFFFFF;
|
||||||
f(x + off_x, y + off_y, color);
|
f(x + off_x, y + off_y, color);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Content::Color => {
|
}
|
||||||
let mut i = 0;
|
Content::Color => {
|
||||||
for off_y in 0..image.placement.height as i32 {
|
let mut i = 0;
|
||||||
for off_x in 0..image.placement.width as i32 {
|
for off_y in 0..image.placement.height as i32 {
|
||||||
//TODO: blend base alpha?
|
for off_x in 0..image.placement.width as i32 {
|
||||||
let color = (image.data[i + 3] as u32) << 24
|
//TODO: blend base alpha?
|
||||||
| (image.data[i] as u32) << 16
|
let color = (image.data[i + 3] as u32) << 24
|
||||||
| (image.data[i + 1] as u32) << 8
|
| (image.data[i] as u32) << 16
|
||||||
| (image.data[i + 2] as u32);
|
| (image.data[i + 1] as u32) << 8
|
||||||
f(x + off_x, y + off_y, color);
|
| (image.data[i + 2] as u32);
|
||||||
i += 4;
|
f(x + off_x, y + off_y, color);
|
||||||
}
|
i += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Content::SubpixelMask => {
|
}
|
||||||
log::warn!("TODO: SubpixelMask");
|
Content::SubpixelMask => {
|
||||||
}
|
log::warn!("TODO: SubpixelMask");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue