Merge pull request #122 from hecrj/feature/shaping-switch
`Shaping` strategy selection
This commit is contained in:
commit
9062cce140
8 changed files with 144 additions and 24 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use cosmic_text::{
|
||||
Action, Attrs, AttrsList, Buffer, BufferLine, Color, Edit, Editor, Family, FontSystem, Metrics,
|
||||
Style, SwashCache, Weight,
|
||||
Shaping, Style, SwashCache, Weight,
|
||||
};
|
||||
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
||||
use std::{
|
||||
|
|
@ -143,7 +143,7 @@ fn main() {
|
|||
editor
|
||||
.buffer_mut()
|
||||
.lines
|
||||
.push(BufferLine::new(line_text, attrs_list));
|
||||
.push(BufferLine::new(line_text, attrs_list, Shaping::Advanced));
|
||||
}
|
||||
|
||||
let mut swash_cache = SwashCache::new();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||
|
||||
use cosmic_text::{Attrs, Buffer, Color, FontSystem, Metrics, SwashCache};
|
||||
use cosmic_text::{Attrs, Buffer, Color, FontSystem, Metrics, Shaping, SwashCache};
|
||||
use std::cmp::{self, Ordering};
|
||||
use termion::{color, cursor};
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ fn main() {
|
|||
let attrs = Attrs::new();
|
||||
|
||||
// Add some text!
|
||||
buffer.set_text(" Hi, Rust! 🦀", attrs);
|
||||
buffer.set_text(" Hi, Rust! 🦀", attrs, Shaping::Advanced);
|
||||
|
||||
// Perform shaping as desired
|
||||
buffer.shape_until_scroll();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||
use crate::Color;
|
||||
use crate::{
|
||||
Attrs, AttrsList, BorrowedWithFontSystem, BufferLine, FontSystem, LayoutGlyph, LayoutLine,
|
||||
ShapeLine, Wrap,
|
||||
ShapeLine, Shaping, Wrap,
|
||||
};
|
||||
|
||||
/// Current cursor location
|
||||
|
|
@ -331,7 +331,7 @@ impl Buffer {
|
|||
redraw: false,
|
||||
wrap: Wrap::Word,
|
||||
};
|
||||
buffer.set_text(font_system, "", Attrs::new());
|
||||
buffer.set_text(font_system, "", Attrs::new(), Shaping::Advanced);
|
||||
buffer
|
||||
}
|
||||
|
||||
|
|
@ -562,16 +562,28 @@ impl Buffer {
|
|||
}
|
||||
|
||||
/// Set text of buffer, using provided attributes for each line by default
|
||||
pub fn set_text(&mut self, font_system: &mut FontSystem, text: &str, attrs: Attrs) {
|
||||
pub fn set_text(
|
||||
&mut self,
|
||||
font_system: &mut FontSystem,
|
||||
text: &str,
|
||||
attrs: Attrs,
|
||||
shaping: Shaping,
|
||||
) {
|
||||
self.lines.clear();
|
||||
for line in text.lines() {
|
||||
self.lines
|
||||
.push(BufferLine::new(line.to_string(), AttrsList::new(attrs)));
|
||||
self.lines.push(BufferLine::new(
|
||||
line.to_string(),
|
||||
AttrsList::new(attrs),
|
||||
shaping,
|
||||
));
|
||||
}
|
||||
// Make sure there is always one line
|
||||
if self.lines.is_empty() {
|
||||
self.lines
|
||||
.push(BufferLine::new(String::new(), AttrsList::new(attrs)));
|
||||
self.lines.push(BufferLine::new(
|
||||
String::new(),
|
||||
AttrsList::new(attrs),
|
||||
shaping,
|
||||
));
|
||||
}
|
||||
|
||||
self.scroll = 0;
|
||||
|
|
@ -769,8 +781,8 @@ impl<'a> BorrowedWithFontSystem<'a, Buffer> {
|
|||
}
|
||||
|
||||
/// Set text of buffer, using provided attributes for each line by default
|
||||
pub fn set_text(&mut self, text: &str, attrs: Attrs) {
|
||||
self.inner.set_text(self.font_system, text, attrs);
|
||||
pub fn set_text(&mut self, text: &str, attrs: Attrs, shaping: Shaping) {
|
||||
self.inner.set_text(self.font_system, text, attrs, shaping);
|
||||
}
|
||||
|
||||
/// Draw the buffer
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{string::String, vec::Vec};
|
||||
|
||||
use crate::{Align, AttrsList, FontSystem, LayoutLine, ShapeLine, Wrap};
|
||||
use crate::{Align, AttrsList, FontSystem, LayoutLine, ShapeLine, Shaping, Wrap};
|
||||
|
||||
/// A line (or paragraph) of text that is shaped and laid out
|
||||
pub struct BufferLine {
|
||||
|
|
@ -12,13 +12,14 @@ pub struct BufferLine {
|
|||
align: Option<Align>,
|
||||
shape_opt: Option<ShapeLine>,
|
||||
layout_opt: Option<Vec<LayoutLine>>,
|
||||
shaping: Shaping,
|
||||
}
|
||||
|
||||
impl BufferLine {
|
||||
/// Create a new line with the given text and attributes list
|
||||
/// Cached shaping and layout can be done using the [`Self::shape`] and
|
||||
/// [`Self::layout`] functions
|
||||
pub fn new<T: Into<String>>(text: T, attrs_list: AttrsList) -> Self {
|
||||
pub fn new<T: Into<String>>(text: T, attrs_list: AttrsList, shaping: Shaping) -> Self {
|
||||
Self {
|
||||
text: text.into(),
|
||||
attrs_list,
|
||||
|
|
@ -26,6 +27,7 @@ impl BufferLine {
|
|||
align: None,
|
||||
shape_opt: None,
|
||||
layout_opt: None,
|
||||
shaping,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +144,7 @@ impl BufferLine {
|
|||
let attrs_list = self.attrs_list.split_off(index);
|
||||
self.reset();
|
||||
|
||||
let mut new = Self::new(text, attrs_list);
|
||||
let mut new = Self::new(text, attrs_list, self.shaping);
|
||||
new.wrap = self.wrap;
|
||||
new
|
||||
}
|
||||
|
|
@ -167,7 +169,12 @@ impl BufferLine {
|
|||
/// Shape line, will cache results
|
||||
pub fn shape(&mut self, font_system: &mut FontSystem) -> &ShapeLine {
|
||||
if self.shape_opt.is_none() {
|
||||
self.shape_opt = Some(ShapeLine::new(font_system, &self.text, &self.attrs_list));
|
||||
self.shape_opt = Some(ShapeLine::new(
|
||||
font_system,
|
||||
&self.text,
|
||||
&self.attrs_list,
|
||||
self.shaping,
|
||||
));
|
||||
self.layout_opt = None;
|
||||
}
|
||||
self.shape_opt.as_ref().expect("shape not found")
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||
use crate::Color;
|
||||
use crate::{
|
||||
Action, Affinity, AttrsList, Buffer, BufferLine, Cursor, Edit, FontSystem, LayoutCursor,
|
||||
Shaping,
|
||||
};
|
||||
|
||||
/// A wrapper of [`Buffer`] for easy editing
|
||||
|
|
@ -245,6 +246,7 @@ impl Edit for Editor {
|
|||
.strip_suffix(char::is_control)
|
||||
.unwrap_or(data_line),
|
||||
these_attrs,
|
||||
Shaping::Advanced,
|
||||
));
|
||||
} else {
|
||||
panic!("str::lines() did not yield any elements");
|
||||
|
|
@ -256,6 +258,7 @@ impl Edit for Editor {
|
|||
.strip_suffix(char::is_control)
|
||||
.unwrap_or(data_line),
|
||||
final_attrs.split_off(remaining_split_len),
|
||||
Shaping::Advanced,
|
||||
);
|
||||
tmp.append(after);
|
||||
self.buffer.lines.insert(insert_line, tmp);
|
||||
|
|
@ -270,6 +273,7 @@ impl Edit for Editor {
|
|||
.strip_suffix(char::is_control)
|
||||
.unwrap_or(data_line),
|
||||
final_attrs.split_off(remaining_split_len),
|
||||
Shaping::Advanced,
|
||||
);
|
||||
self.buffer.lines.insert(insert_line, tmp);
|
||||
self.cursor.line += 1;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
|
|||
|
||||
use crate::{
|
||||
Action, AttrsList, BorrowedWithFontSystem, Buffer, Color, Cursor, Edit, Editor, FontSystem,
|
||||
Style, Weight, Wrap,
|
||||
Shaping, Style, Weight, Wrap,
|
||||
};
|
||||
|
||||
pub struct SyntaxSystem {
|
||||
|
|
@ -75,7 +75,9 @@ impl<'a> SyntaxEditor<'a> {
|
|||
let path = path.as_ref();
|
||||
|
||||
let text = fs::read_to_string(path)?;
|
||||
self.editor.buffer_mut().set_text(font_system, &text, attrs);
|
||||
self.editor
|
||||
.buffer_mut()
|
||||
.set_text(font_system, &text, attrs, Shaping::Advanced);
|
||||
|
||||
//TODO: re-use text
|
||||
self.syntax = match self.syntax_system.syntax_set.find_syntax_for_file(path) {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
//! point, you can use the `SwashCache` to rasterize glyphs into either images or pixels.
|
||||
//!
|
||||
//! ```
|
||||
//! use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics};
|
||||
//! use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics, Shaping};
|
||||
//!
|
||||
//! // A FontSystem provides access to detected system fonts, create one per application
|
||||
//! let mut font_system = FontSystem::new();
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
//! let attrs = Attrs::new();
|
||||
//!
|
||||
//! // Add some text!
|
||||
//! buffer.set_text("Hello, Rust! 🦀\n", attrs);
|
||||
//! buffer.set_text("Hello, Rust! 🦀\n", attrs, Shaping::Advanced);
|
||||
//!
|
||||
//! // Perform shaping as desired
|
||||
//! buffer.shape_until_scroll();
|
||||
|
|
|
|||
101
src/shape.rs
101
src/shape.rs
|
|
@ -11,6 +11,46 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||
use crate::fallback::FontFallbackIter;
|
||||
use crate::{Align, AttrsList, CacheKey, Color, Font, FontSystem, LayoutGlyph, LayoutLine, Wrap};
|
||||
|
||||
/// The shaping strategy of some text.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Shaping {
|
||||
/// Basic shaping with no font fallback.
|
||||
///
|
||||
/// This shaping strategy is very cheap, but it will not display complex
|
||||
/// scripts properly nor try to find missing glyphs in your system fonts.
|
||||
///
|
||||
/// You should use this strategy when you have complete control of the text
|
||||
/// and the font you are displaying in your application.
|
||||
#[cfg(feature = "swash")]
|
||||
Basic,
|
||||
/// Advanced text shaping and font fallback.
|
||||
///
|
||||
/// You will need to enable this strategy if the text contains a complex
|
||||
/// script, the font used needs it, and/or multiple fonts in your system
|
||||
/// may be needed to display all of the glyphs.
|
||||
Advanced,
|
||||
}
|
||||
|
||||
impl Shaping {
|
||||
fn run(
|
||||
self,
|
||||
font_system: &mut FontSystem,
|
||||
line: &str,
|
||||
attrs_list: &AttrsList,
|
||||
start_run: usize,
|
||||
end_run: usize,
|
||||
span_rtl: bool,
|
||||
) -> Vec<ShapeGlyph> {
|
||||
match self {
|
||||
#[cfg(feature = "swash")]
|
||||
Self::Basic => shape_skip(font_system, line, attrs_list, start_run, end_run),
|
||||
Self::Advanced => {
|
||||
shape_run(font_system, line, attrs_list, start_run, end_run, span_rtl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn shape_fallback(
|
||||
font: &Font,
|
||||
line: &str,
|
||||
|
|
@ -213,6 +253,50 @@ fn shape_run(
|
|||
glyphs
|
||||
}
|
||||
|
||||
#[cfg(feature = "swash")]
|
||||
fn shape_skip(
|
||||
font_system: &mut FontSystem,
|
||||
line: &str,
|
||||
attrs_list: &AttrsList,
|
||||
start_run: usize,
|
||||
end_run: usize,
|
||||
) -> Vec<ShapeGlyph> {
|
||||
let attrs = attrs_list.get_span(start_run);
|
||||
let fonts = font_system.get_font_matches(attrs);
|
||||
|
||||
let default_families = [&attrs.family];
|
||||
let mut font_iter = FontFallbackIter::new(font_system, &fonts, &default_families, Vec::new());
|
||||
|
||||
let font = font_iter.next().expect("no default font found");
|
||||
let font_id = font.id();
|
||||
let font = font.as_swash();
|
||||
|
||||
let charmap = font.charmap();
|
||||
let glyph_metrics = font.glyph_metrics(&[]).scale(1.0);
|
||||
|
||||
line[start_run..end_run]
|
||||
.chars()
|
||||
.enumerate()
|
||||
.map(|(i, codepoint)| {
|
||||
let glyph_id = charmap.map(codepoint);
|
||||
let x_advance = glyph_metrics.advance_width(glyph_id);
|
||||
|
||||
ShapeGlyph {
|
||||
start: i,
|
||||
end: i + 1,
|
||||
x_advance,
|
||||
y_advance: 0.0,
|
||||
x_offset: 0.0,
|
||||
y_offset: 0.0,
|
||||
font_id,
|
||||
glyph_id,
|
||||
color_opt: attrs.color_opt,
|
||||
metadata: attrs.metadata,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// A shaped glyph
|
||||
pub struct ShapeGlyph {
|
||||
pub start: usize,
|
||||
|
|
@ -278,6 +362,7 @@ impl ShapeWord {
|
|||
word_range: Range<usize>,
|
||||
level: unicode_bidi::Level,
|
||||
blank: bool,
|
||||
shaping: Shaping,
|
||||
) -> Self {
|
||||
let word = &line[word_range.clone()];
|
||||
|
||||
|
|
@ -297,7 +382,7 @@ impl ShapeWord {
|
|||
let attrs_egc = attrs_list.get_span(start_egc);
|
||||
if !attrs.compatible(&attrs_egc) {
|
||||
//TODO: more efficient
|
||||
glyphs.append(&mut shape_run(
|
||||
glyphs.append(&mut shaping.run(
|
||||
font_system,
|
||||
line,
|
||||
attrs_list,
|
||||
|
|
@ -312,7 +397,7 @@ impl ShapeWord {
|
|||
}
|
||||
if start_run < word_range.end {
|
||||
//TODO: more efficient
|
||||
glyphs.append(&mut shape_run(
|
||||
glyphs.append(&mut shaping.run(
|
||||
font_system,
|
||||
line,
|
||||
attrs_list,
|
||||
|
|
@ -352,6 +437,7 @@ impl ShapeSpan {
|
|||
span_range: Range<usize>,
|
||||
line_rtl: bool,
|
||||
level: unicode_bidi::Level,
|
||||
shaping: Shaping,
|
||||
) -> Self {
|
||||
let span = &line[span_range.start..span_range.end];
|
||||
|
||||
|
|
@ -382,6 +468,7 @@ impl ShapeSpan {
|
|||
(span_range.start + start_word)..(span_range.start + start_lb),
|
||||
level,
|
||||
false,
|
||||
shaping,
|
||||
));
|
||||
}
|
||||
if start_lb < end_lb {
|
||||
|
|
@ -395,6 +482,7 @@ impl ShapeSpan {
|
|||
..(span_range.start + start_lb + i + c.len_utf8()),
|
||||
level,
|
||||
true,
|
||||
shaping,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -437,7 +525,12 @@ impl ShapeLine {
|
|||
/// # Panics
|
||||
///
|
||||
/// Will panic if `line` contains more than one paragraph.
|
||||
pub fn new(font_system: &mut FontSystem, line: &str, attrs_list: &AttrsList) -> Self {
|
||||
pub fn new(
|
||||
font_system: &mut FontSystem,
|
||||
line: &str,
|
||||
attrs_list: &AttrsList,
|
||||
shaping: Shaping,
|
||||
) -> Self {
|
||||
let mut spans = Vec::new();
|
||||
|
||||
let bidi = unicode_bidi::BidiInfo::new(line, None);
|
||||
|
|
@ -473,6 +566,7 @@ impl ShapeLine {
|
|||
start..i,
|
||||
line_rtl,
|
||||
run_level,
|
||||
shaping,
|
||||
));
|
||||
start = i;
|
||||
run_level = new_level;
|
||||
|
|
@ -485,6 +579,7 @@ impl ShapeLine {
|
|||
start..line_range.end,
|
||||
line_rtl,
|
||||
run_level,
|
||||
shaping,
|
||||
));
|
||||
line_rtl
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue