Merge branch 'mut-font-system' of https://github.com/geieredgar/cosmic-text into geieredgar-mut-font-system

This commit is contained in:
Jeremy Soller 2023-03-17 18:15:45 -06:00
commit 9ebbc33792
23 changed files with 650 additions and 453 deletions

View file

@ -12,7 +12,7 @@ repository = "https://github.com/pop-os/cosmic-text"
fontdb = { version = "0.13.0", default-features = false } fontdb = { version = "0.13.0", default-features = false }
libm = "0.2.6" libm = "0.2.6"
log = "0.4.17" log = "0.4.17"
ouroboros = "0.15.5" ouroboros = { version = "0.15.5", default-features = false }
rustybuzz = { version = "0.7.0", default-features = false, features = ["libm"] } rustybuzz = { version = "0.7.0", default-features = false, features = ["libm"] }
swash = { version = "0.1.6", optional = true } swash = { version = "0.1.6", optional = true }
syntect = { version = "5.0.0", optional = true } syntect = { version = "5.0.0", optional = true }
@ -35,6 +35,7 @@ no_std = [
std = [ std = [
"fontdb/memmap", "fontdb/memmap",
"fontdb/std", "fontdb/std",
"ouroboros/std",
"rustybuzz/std", "rustybuzz/std",
"sys-locale", "sys-locale",
"unicode-bidi/std", "unicode-bidi/std",

View file

@ -23,7 +23,7 @@ use self::text_box::text_box;
mod text_box; mod text_box;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref FONT_SYSTEM: FontSystem = FontSystem::new(); static ref FONT_SYSTEM: Mutex<FontSystem> = Mutex::new(FontSystem::new());
static ref SYNTAX_SYSTEM: SyntaxSystem = SyntaxSystem::new(); static ref SYNTAX_SYSTEM: SyntaxSystem = SyntaxSystem::new();
} }
@ -112,6 +112,8 @@ pub enum Message {
impl Window { impl Window {
pub fn open(&mut self, path: PathBuf) { pub fn open(&mut self, path: PathBuf) {
let mut editor = self.editor.lock().unwrap(); let mut editor = self.editor.lock().unwrap();
let mut font_system = FONT_SYSTEM.lock().unwrap();
let mut editor = editor.borrow_with(&mut font_system);
match editor.load_text(&path, self.attrs) { match editor.load_text(&path, self.attrs) {
Ok(()) => { Ok(()) => {
log::info!("opened '{}'", path.display()); log::info!("opened '{}'", path.display());
@ -137,7 +139,10 @@ impl Application for Window {
.family(cosmic_text::Family::Monospace); .family(cosmic_text::Family::Monospace);
let mut editor = SyntaxEditor::new( let mut editor = SyntaxEditor::new(
Buffer::new(&FONT_SYSTEM, FontSize::Body.to_metrics()), Buffer::new(
&mut FONT_SYSTEM.lock().unwrap(),
FontSize::Body.to_metrics(),
),
&SYNTAX_SYSTEM, &SYNTAX_SYSTEM,
"base16-eighties.dark", "base16-eighties.dark",
) )
@ -169,11 +174,11 @@ impl Application for Window {
if let Some(path) = &self.path_opt { if let Some(path) = &self.path_opt {
format!( format!(
"COSMIC Text - {} - {}", "COSMIC Text - {} - {}",
FONT_SYSTEM.locale(), FONT_SYSTEM.lock().unwrap().locale(),
path.display() path.display()
) )
} else { } else {
format!("COSMIC Text - {}", FONT_SYSTEM.locale()) format!("COSMIC Text - {}", FONT_SYSTEM.lock().unwrap().locale())
} }
} }
@ -237,13 +242,18 @@ impl Application for Window {
} }
Message::FontSizeChanged(font_size) => { Message::FontSizeChanged(font_size) => {
self.font_size = font_size; self.font_size = font_size;
let mut editor = self.editor.lock().unwrap(); let mut editor = self.editor.lock().unwrap();
editor.buffer_mut().set_metrics(font_size.to_metrics()); editor
.borrow_with(&mut FONT_SYSTEM.lock().unwrap())
.buffer_mut()
.set_metrics(font_size.to_metrics());
} }
Message::WrapChanged(wrap) => { Message::WrapChanged(wrap) => {
let mut editor = self.editor.lock().unwrap(); let mut editor = self.editor.lock().unwrap();
editor.buffer_mut().set_wrap(wrap); editor
.borrow_with(&mut FONT_SYSTEM.lock().unwrap())
.buffer_mut()
.set_wrap(wrap);
} }
Message::AlignmentChanged(align) => { Message::AlignmentChanged(align) => {
let mut editor = self.editor.lock().unwrap(); let mut editor = self.editor.lock().unwrap();
@ -360,13 +370,13 @@ impl Application for Window {
} }
} }
fn update_attrs<'a, T: Edit<'a>>(editor: &mut T, attrs: Attrs<'a>) { fn update_attrs<T: Edit>(editor: &mut T, attrs: Attrs) {
editor.buffer_mut().lines.iter_mut().for_each(|line| { editor.buffer_mut().lines.iter_mut().for_each(|line| {
line.set_attrs_list(AttrsList::new(attrs)); line.set_attrs_list(AttrsList::new(attrs));
}); });
} }
fn update_alignment<'a, T: Edit<'a>>(editor: &mut T, align: Align) { fn update_alignment<T: Edit>(editor: &mut T, align: Align) {
let current_line = editor.cursor().line; let current_line = editor.cursor().line;
if let Some(select) = editor.select_opt() { if let Some(select) = editor.select_opt() {
let (start, end) = match select.line.cmp(&current_line) { let (start, end) = match select.line.cmp(&current_line) {

View file

@ -53,7 +53,7 @@ impl Text {
let mut line = BufferLine::new(string, AttrsList::new(Attrs::new())); let mut line = BufferLine::new(string, AttrsList::new(Attrs::new()));
//TODO: do we have to immediately shape? //TODO: do we have to immediately shape?
line.shape(&crate::FONT_SYSTEM); line.shape(&mut FONT_SYSTEM.lock().unwrap());
let text = Self { let text = Self {
line, line,
@ -186,7 +186,7 @@ where
}; };
cache.with_pixels( cache.with_pixels(
&FONT_SYSTEM, &mut FONT_SYSTEM.lock().unwrap(),
cache_key, cache_key,
glyph_color, glyph_color,
|pixel_x, pixel_y, color| { |pixel_x, pixel_y, color| {

View file

@ -1,5 +1,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use crate::FONT_SYSTEM;
use super::text; use super::text;
use cosmic::{ use cosmic::{
iced_native::{ iced_native::{
@ -66,11 +68,11 @@ pub fn text_box<Editor>(editor: &Mutex<Editor>) -> TextBox<Editor> {
TextBox::new(editor) TextBox::new(editor)
} }
impl<'a, 'editor, Editor, Message, Renderer> Widget<Message, Renderer> for TextBox<'a, Editor> impl<'a, Editor, Message, Renderer> Widget<Message, Renderer> for TextBox<'a, Editor>
where where
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>, Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
Renderer::Theme: StyleSheet, Renderer::Theme: StyleSheet,
Editor: Edit<'editor>, Editor: Edit,
{ {
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>() tree::Tag::of::<State>()
@ -93,7 +95,10 @@ where
//TODO: allow lazy shape //TODO: allow lazy shape
let mut editor = self.editor.lock().unwrap(); let mut editor = self.editor.lock().unwrap();
editor.buffer_mut().shape_until(i32::max_value()); editor
.borrow_with(&mut FONT_SYSTEM.lock().unwrap())
.buffer_mut()
.shape_until(i32::max_value());
let mut layout_lines = 0; let mut layout_lines = 0;
for line in editor.buffer().lines.iter() { for line in editor.buffer().lines.iter() {
@ -162,6 +167,10 @@ where
let view_w = viewport.width.min(layout.bounds().width) - self.padding.horizontal() as f32; let view_w = viewport.width.min(layout.bounds().width) - self.padding.horizontal() as f32;
let view_h = viewport.height.min(layout.bounds().height) - self.padding.vertical() as f32; let view_h = viewport.height.min(layout.bounds().height) - self.padding.vertical() as f32;
let mut font_system = FONT_SYSTEM.lock().unwrap();
let mut editor = editor.borrow_with(&mut font_system);
editor.buffer_mut().set_size(view_w, view_h); editor.buffer_mut().set_size(view_w, view_h);
editor.shape_as_needed(); editor.shape_as_needed();
@ -232,6 +241,8 @@ where
) -> Status { ) -> Status {
let state = tree.state.downcast_mut::<State>(); let state = tree.state.downcast_mut::<State>();
let mut editor = self.editor.lock().unwrap(); let mut editor = self.editor.lock().unwrap();
let mut font_system = FONT_SYSTEM.lock().unwrap();
let mut editor = editor.borrow_with(&mut font_system);
let mut status = Status::Ignored; let mut status = Status::Ignored;
match event { match event {
@ -330,12 +341,11 @@ where
} }
} }
impl<'a, 'editor, Editor, Message, Renderer> From<TextBox<'a, Editor>> impl<'a, Editor, Message, Renderer> From<TextBox<'a, Editor>> for Element<'a, Message, Renderer>
for Element<'a, Message, Renderer>
where where
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>, Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
Renderer::Theme: StyleSheet, Renderer::Theme: StyleSheet,
Editor: Edit<'editor>, Editor: Edit,
{ {
fn from(text_box: TextBox<'a, Editor>) -> Self { fn from(text_box: TextBox<'a, Editor>) -> Self {
Self::new(text_box) Self::new(text_box)

View file

@ -40,7 +40,7 @@ fn main() {
) )
.unwrap(); .unwrap();
let font_system = FontSystem::new(); let mut font_system = FontSystem::new();
let syntax_system = SyntaxSystem::new(); let syntax_system = SyntaxSystem::new();
@ -58,7 +58,7 @@ fn main() {
let line_x = 8.0 * display_scale; let line_x = 8.0 * display_scale;
let mut editor = SyntaxEditor::new( let mut editor = SyntaxEditor::new(
Buffer::new(&font_system, font_sizes[font_size_i]), Buffer::new(&mut font_system, font_sizes[font_size_i]),
&syntax_system, &syntax_system,
"base16-eighties.dark", "base16-eighties.dark",
) )
@ -67,6 +67,8 @@ fn main() {
#[cfg(feature = "vi")] #[cfg(feature = "vi")]
let mut editor = cosmic_text::ViEditor::new(editor); let mut editor = cosmic_text::ViEditor::new(editor);
let mut editor = editor.borrow_with(&mut font_system);
editor editor
.buffer_mut() .buffer_mut()
.set_size(window.width() as f32 - line_x * 2.0, window.height() as f32); .set_size(window.width() as f32 - line_x * 2.0, window.height() as f32);

View file

@ -1,11 +1,17 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use cosmic_text::{Action, Buffer, Color, Edit, Editor, FontSystem, Metrics, SwashCache}; use cosmic_text::{
Action, BorrowedWithFontSystem, Buffer, Color, Edit, Editor, FontSystem, Metrics, SwashCache,
};
use orbclient::{EventOption, Renderer, Window, WindowFlag}; use orbclient::{EventOption, Renderer, Window, WindowFlag};
use std::{env, fs, process, time::Instant}; use std::{env, fs, process, time::Instant};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
fn redraw(window: &mut Window, editor: &mut Editor<'_>, swash_cache: &mut SwashCache) { fn redraw(
window: &mut Window,
editor: &mut BorrowedWithFontSystem<Editor>,
swash_cache: &mut SwashCache,
) {
let bg_color = orbclient::Color::rgb(0x34, 0x34, 0x34); let bg_color = orbclient::Color::rgb(0x34, 0x34, 0x34);
let font_color = Color::rgb(0xFF, 0xFF, 0xFF); let font_color = Color::rgb(0xFF, 0xFF, 0xFF);
@ -32,7 +38,7 @@ fn main() {
env_logger::init(); env_logger::init();
let display_scale = 1.0; let display_scale = 1.0;
let font_system = FontSystem::new(); let mut font_system = FontSystem::new();
let mut window = Window::new_flags( let mut window = Window::new_flags(
-1, -1,
@ -54,11 +60,15 @@ fn main() {
]; ];
let font_size_default = 1; // Body let font_size_default = 1; // Body
let mut buffer = Buffer::new(&font_system, font_sizes[font_size_default]); let mut buffer = Buffer::new(&mut font_system, font_sizes[font_size_default]);
buffer.set_size(window.width() as f32, window.height() as f32); buffer
.borrow_with(&mut font_system)
.set_size(window.width() as f32, window.height() as f32);
let mut editor = Editor::new(buffer); let mut editor = Editor::new(buffer);
let mut editor = editor.borrow_with(&mut font_system);
let mut swash_cache = SwashCache::new(); 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) {

View file

@ -13,7 +13,7 @@ use std::{
fn main() { fn main() {
env_logger::init(); env_logger::init();
let font_system = FontSystem::new(); let mut font_system = FontSystem::new();
let display_scale = match orbclient::get_display_size() { let display_scale = match orbclient::get_display_size() {
Ok((w, h)) => { Ok((w, h)) => {
@ -37,10 +37,12 @@ fn main() {
.unwrap(); .unwrap();
let mut editor = Editor::new(Buffer::new( let mut editor = Editor::new(Buffer::new(
&font_system, &mut font_system,
Metrics::new(32.0, 44.0).scale(display_scale), Metrics::new(32.0, 44.0).scale(display_scale),
)); ));
let mut editor = editor.borrow_with(&mut font_system);
editor editor
.buffer_mut() .buffer_mut()
.set_size(window.width() as f32, window.height() as f32); .set_size(window.width() as f32, window.height() as f32);

View file

@ -6,7 +6,7 @@ use termion::{color, cursor};
fn main() { fn main() {
// A FontSystem provides access to detected system fonts, create one per application // A FontSystem provides access to detected system fonts, create one per application
let font_system = FontSystem::new(); let mut font_system = FontSystem::new();
// A SwashCache stores rasterized glyphs, create one per application // A SwashCache stores rasterized glyphs, create one per application
let mut swash_cache = SwashCache::new(); let mut swash_cache = SwashCache::new();
@ -15,7 +15,9 @@ fn main() {
let metrics = Metrics::new(14.0, 20.0); let metrics = Metrics::new(14.0, 20.0);
// A Buffer provides shaping and layout for a UTF-8 string, create one per text widget // A Buffer provides shaping and layout for a UTF-8 string, create one per text widget
let mut buffer = Buffer::new(&font_system, metrics); let mut buffer = Buffer::new(&mut font_system, metrics);
let mut buffer = buffer.borrow_with(&mut font_system);
// Set a size for the text buffer, in pixels // Set a size for the text buffer, in pixels
let width = 80u16; let width = 80u16;

View file

@ -10,7 +10,10 @@ use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
use crate::Color; use crate::Color;
use crate::{Attrs, AttrsList, BufferLine, FontSystem, LayoutGlyph, LayoutLine, ShapeLine, Wrap}; use crate::{
Attrs, AttrsList, BorrowedWithFontSystem, BufferLine, FontSystem, LayoutGlyph, LayoutLine,
ShapeLine, Wrap,
};
/// Current cursor location /// Current cursor location
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
@ -169,8 +172,8 @@ impl<'a> LayoutRun<'a> {
} }
/// An iterator of visible text lines, see [`LayoutRun`] /// An iterator of visible text lines, see [`LayoutRun`]
pub struct LayoutRunIter<'a, 'b> { pub struct LayoutRunIter<'b> {
buffer: &'b Buffer<'a>, buffer: &'b Buffer,
line_i: usize, line_i: usize,
layout_i: usize, layout_i: usize,
remaining_len: usize, remaining_len: usize,
@ -178,8 +181,8 @@ pub struct LayoutRunIter<'a, 'b> {
total_layout: i32, total_layout: i32,
} }
impl<'a, 'b> LayoutRunIter<'a, 'b> { impl<'b> LayoutRunIter<'b> {
pub fn new(buffer: &'b Buffer<'a>) -> Self { pub fn new(buffer: &'b Buffer) -> Self {
let total_layout_lines: usize = buffer let total_layout_lines: usize = buffer
.lines .lines
.iter() .iter()
@ -215,7 +218,7 @@ impl<'a, 'b> LayoutRunIter<'a, 'b> {
} }
} }
impl<'a, 'b> Iterator for LayoutRunIter<'a, 'b> { impl<'b> Iterator for LayoutRunIter<'b> {
type Item = LayoutRun<'b>; type Item = LayoutRun<'b>;
fn size_hint(&self) -> (usize, Option<usize>) { fn size_hint(&self) -> (usize, Option<usize>) {
@ -258,7 +261,7 @@ impl<'a, 'b> Iterator for LayoutRunIter<'a, 'b> {
} }
} }
impl<'a, 'b> ExactSizeIterator for LayoutRunIter<'a, 'b> {} impl<'b> ExactSizeIterator for LayoutRunIter<'b> {}
/// Metrics of text /// Metrics of text
#[derive(Clone, Copy, Debug, Default, PartialEq)] #[derive(Clone, Copy, Debug, Default, PartialEq)]
@ -296,8 +299,7 @@ impl fmt::Display for Metrics {
} }
/// A buffer of text that is shaped and laid out /// A buffer of text that is shaped and laid out
pub struct Buffer<'a> { pub struct Buffer {
font_system: &'a FontSystem,
/// [BufferLine]s (or paragraphs) of text in the buffer /// [BufferLine]s (or paragraphs) of text in the buffer
pub lines: Vec<BufferLine>, pub lines: Vec<BufferLine>,
metrics: Metrics, metrics: Metrics,
@ -309,17 +311,16 @@ pub struct Buffer<'a> {
wrap: Wrap, wrap: Wrap,
} }
impl<'a> Buffer<'a> { impl Buffer {
/// Create a new [`Buffer`] with the provided [`FontSystem`] and [`Metrics`] /// Create a new [`Buffer`] with the provided [`FontSystem`] and [`Metrics`]
/// ///
/// # Panics /// # Panics
/// ///
/// Will panic if `metrics.line_height` is zero. /// Will panic if `metrics.line_height` is zero.
pub fn new(font_system: &'a FontSystem, metrics: Metrics) -> Self { pub fn new(font_system: &mut FontSystem, metrics: Metrics) -> Self {
assert_ne!(metrics.line_height, 0.0, "line height cannot be 0"); assert_ne!(metrics.line_height, 0.0, "line height cannot be 0");
let mut buffer = Self { let mut buffer = Self {
font_system,
lines: Vec::new(), lines: Vec::new(),
metrics, metrics,
width: 0.0, width: 0.0,
@ -328,23 +329,29 @@ impl<'a> Buffer<'a> {
redraw: false, redraw: false,
wrap: Wrap::Word, wrap: Wrap::Word,
}; };
buffer.set_text("", Attrs::new()); buffer.set_text(font_system, "", Attrs::new());
buffer buffer
} }
fn relayout(&mut self) { /// Mutably borrows the buffer together with an [`FontSystem`] for more convenient methods
pub fn borrow_with<'a>(
&'a mut self,
font_system: &'a mut FontSystem,
) -> BorrowedWithFontSystem<'a, Buffer> {
BorrowedWithFontSystem {
inner: self,
font_system,
}
}
fn relayout(&mut self, font_system: &mut FontSystem) {
#[cfg(all(feature = "std", not(target_arch = "wasm32")))] #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
let instant = std::time::Instant::now(); let instant = std::time::Instant::now();
for line in &mut self.lines { for line in &mut self.lines {
if line.shape_opt().is_some() { if line.shape_opt().is_some() {
line.reset_layout(); line.reset_layout();
line.layout( line.layout(font_system, self.metrics.font_size, self.width, self.wrap);
self.font_system,
self.metrics.font_size,
self.width,
self.wrap,
);
} }
} }
@ -355,7 +362,7 @@ impl<'a> Buffer<'a> {
} }
/// Pre-shape lines in the buffer, up to `lines`, return actual number of layout lines /// Pre-shape lines in the buffer, up to `lines`, return actual number of layout lines
pub fn shape_until(&mut self, lines: i32) -> i32 { pub fn shape_until(&mut self, font_system: &mut FontSystem, lines: i32) -> i32 {
#[cfg(all(feature = "std", not(target_arch = "wasm32")))] #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
let instant = std::time::Instant::now(); let instant = std::time::Instant::now();
@ -369,12 +376,7 @@ impl<'a> Buffer<'a> {
if line.shape_opt().is_none() { if line.shape_opt().is_none() {
reshaped += 1; reshaped += 1;
} }
let layout = line.layout( let layout = line.layout(font_system, self.metrics.font_size, self.width, self.wrap);
self.font_system,
self.metrics.font_size,
self.width,
self.wrap,
);
total_layout += layout.len() as i32; total_layout += layout.len() as i32;
} }
@ -388,7 +390,7 @@ impl<'a> Buffer<'a> {
} }
/// Shape lines until cursor, also scrolling to include cursor in view /// Shape lines until cursor, also scrolling to include cursor in view
pub fn shape_until_cursor(&mut self, cursor: Cursor) { pub fn shape_until_cursor(&mut self, font_system: &mut FontSystem, cursor: Cursor) {
#[cfg(all(feature = "std", not(target_arch = "wasm32")))] #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
let instant = std::time::Instant::now(); let instant = std::time::Instant::now();
@ -402,12 +404,7 @@ impl<'a> Buffer<'a> {
if line.shape_opt().is_none() { if line.shape_opt().is_none() {
reshaped += 1; reshaped += 1;
} }
let layout = line.layout( let layout = line.layout(font_system, self.metrics.font_size, self.width, self.wrap);
self.font_system,
self.metrics.font_size,
self.width,
self.wrap,
);
if line_i == cursor.line { if line_i == cursor.line {
let layout_cursor = self.layout_cursor(&cursor); let layout_cursor = self.layout_cursor(&cursor);
layout_i += layout_cursor.layout as i32; layout_i += layout_cursor.layout as i32;
@ -430,15 +427,15 @@ impl<'a> Buffer<'a> {
self.scroll = layout_i - (lines - 1); self.scroll = layout_i - (lines - 1);
} }
self.shape_until_scroll(); self.shape_until_scroll(font_system);
} }
/// Shape lines until scroll /// Shape lines until scroll
pub fn shape_until_scroll(&mut self) { pub fn shape_until_scroll(&mut self, font_system: &mut FontSystem) {
let lines = self.visible_lines(); let lines = self.visible_lines();
let scroll_end = self.scroll + lines; let scroll_end = self.scroll + lines;
let total_layout = self.shape_until(scroll_end); let total_layout = self.shape_until(font_system, scroll_end);
self.scroll = cmp::max(0, cmp::min(total_layout - (lines - 1), self.scroll)); self.scroll = cmp::max(0, cmp::min(total_layout - (lines - 1), self.scroll));
} }
@ -473,26 +470,24 @@ impl<'a> Buffer<'a> {
LayoutCursor::new(cursor.line, 0, 0) LayoutCursor::new(cursor.line, 0, 0)
} }
/// Get [`FontSystem`] used by this [`Buffer`]
pub fn font_system(&self) -> &'a FontSystem {
self.font_system
}
/// Shape the provided line index and return the result /// Shape the provided line index and return the result
pub fn line_shape(&mut self, line_i: usize) -> Option<&ShapeLine> { pub fn line_shape(
&mut self,
font_system: &mut FontSystem,
line_i: usize,
) -> Option<&ShapeLine> {
let line = self.lines.get_mut(line_i)?; let line = self.lines.get_mut(line_i)?;
Some(line.shape(self.font_system)) Some(line.shape(font_system))
} }
/// Lay out the provided line index and return the result /// Lay out the provided line index and return the result
pub fn line_layout(&mut self, line_i: usize) -> Option<&[LayoutLine]> { pub fn line_layout(
&mut self,
font_system: &mut FontSystem,
line_i: usize,
) -> Option<&[LayoutLine]> {
let line = self.lines.get_mut(line_i)?; let line = self.lines.get_mut(line_i)?;
Some(line.layout( Some(line.layout(font_system, self.metrics.font_size, self.width, self.wrap))
self.font_system,
self.metrics.font_size,
self.width,
self.wrap,
))
} }
/// Get the current [`Metrics`] /// Get the current [`Metrics`]
@ -505,12 +500,12 @@ impl<'a> Buffer<'a> {
/// # Panics /// # Panics
/// ///
/// Will panic if `metrics.font_size` is zero. /// Will panic if `metrics.font_size` is zero.
pub fn set_metrics(&mut self, metrics: Metrics) { pub fn set_metrics(&mut self, font_system: &mut FontSystem, metrics: Metrics) {
if metrics != self.metrics { if metrics != self.metrics {
assert_ne!(metrics.font_size, 0.0, "font size cannot be 0"); assert_ne!(metrics.font_size, 0.0, "font size cannot be 0");
self.metrics = metrics; self.metrics = metrics;
self.relayout(); self.relayout(font_system);
self.shape_until_scroll(); self.shape_until_scroll(font_system);
} }
} }
@ -520,11 +515,11 @@ impl<'a> Buffer<'a> {
} }
/// Set the current [`Wrap`] /// Set the current [`Wrap`]
pub fn set_wrap(&mut self, wrap: Wrap) { pub fn set_wrap(&mut self, font_system: &mut FontSystem, wrap: Wrap) {
if wrap != self.wrap { if wrap != self.wrap {
self.wrap = wrap; self.wrap = wrap;
self.relayout(); self.relayout(font_system);
self.shape_until_scroll(); self.shape_until_scroll(font_system);
} }
} }
@ -534,15 +529,15 @@ impl<'a> Buffer<'a> {
} }
/// Set the current buffer dimensions /// Set the current buffer dimensions
pub fn set_size(&mut self, width: f32, height: f32) { pub fn set_size(&mut self, font_system: &mut FontSystem, width: f32, height: f32) {
let clamped_width = width.max(0.0); let clamped_width = width.max(0.0);
let clamped_height = height.max(0.0); let clamped_height = height.max(0.0);
if clamped_width != self.width || clamped_height != self.height { if clamped_width != self.width || clamped_height != self.height {
self.width = clamped_width; self.width = clamped_width;
self.height = clamped_height; self.height = clamped_height;
self.relayout(); self.relayout(font_system);
self.shape_until_scroll(); self.shape_until_scroll(font_system);
} }
} }
@ -565,7 +560,7 @@ impl<'a> Buffer<'a> {
} }
/// Set text of buffer, using provided attributes for each line by default /// Set text of buffer, using provided attributes for each line by default
pub fn set_text(&mut self, text: &str, attrs: Attrs<'a>) { pub fn set_text(&mut self, font_system: &mut FontSystem, text: &str, attrs: Attrs) {
self.lines.clear(); self.lines.clear();
for line in text.lines() { for line in text.lines() {
self.lines self.lines
@ -579,7 +574,7 @@ impl<'a> Buffer<'a> {
self.scroll = 0; self.scroll = 0;
self.shape_until_scroll(); self.shape_until_scroll(font_system);
} }
/// True if a redraw is needed /// True if a redraw is needed
@ -593,7 +588,7 @@ impl<'a> Buffer<'a> {
} }
/// Get the visible layout runs for rendering and other tasks /// Get the visible layout runs for rendering and other tasks
pub fn layout_runs<'b>(&'b self) -> LayoutRunIter<'a, 'b> { pub fn layout_runs(&self) -> LayoutRunIter {
LayoutRunIter::new(self) LayoutRunIter::new(self)
} }
@ -700,8 +695,13 @@ impl<'a> Buffer<'a> {
/// Draw the buffer /// Draw the buffer
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
pub fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F) pub fn draw<F>(
where &self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color), F: FnMut(i32, i32, u32, u32, Color),
{ {
for run in self.layout_runs() { for run in self.layout_runs() {
@ -713,10 +713,70 @@ impl<'a> Buffer<'a> {
None => color, None => color,
}; };
cache.with_pixels(self.font_system, cache_key, glyph_color, |x, y, color| { cache.with_pixels(font_system, cache_key, glyph_color, |x, y, color| {
f(x_int + x, run.line_y as i32 + y_int + y, 1, 1, color); f(x_int + x, run.line_y as i32 + y_int + y, 1, 1, color);
}); });
} }
} }
} }
} }
impl<'a> BorrowedWithFontSystem<'a, Buffer> {
/// Pre-shape lines in the buffer, up to `lines`, return actual number of layout lines
pub fn shape_until(&mut self, lines: i32) -> i32 {
self.inner.shape_until(self.font_system, lines)
}
/// Shape lines until cursor, also scrolling to include cursor in view
pub fn shape_until_cursor(&mut self, cursor: Cursor) {
self.inner.shape_until_cursor(self.font_system, cursor);
}
/// Shape lines until scroll
pub fn shape_until_scroll(&mut self) {
self.inner.shape_until_scroll(self.font_system);
}
/// Shape the provided line index and return the result
pub fn line_shape(&mut self, line_i: usize) -> Option<&ShapeLine> {
self.inner.line_shape(self.font_system, line_i)
}
/// Lay out the provided line index and return the result
pub fn line_layout(&mut self, line_i: usize) -> Option<&[LayoutLine]> {
self.inner.line_layout(self.font_system, line_i)
}
/// Set the current [`Metrics`]
///
/// # Panics
///
/// Will panic if `metrics.font_size` is zero.
pub fn set_metrics(&mut self, metrics: Metrics) {
self.inner.set_metrics(self.font_system, metrics);
}
/// Set the current [`Wrap`]
pub fn set_wrap(&mut self, wrap: Wrap) {
self.inner.set_wrap(self.font_system, wrap);
}
/// Set the current buffer dimensions
pub fn set_size(&mut self, width: f32, height: f32) {
self.inner.set_size(self.font_system, width, height);
}
/// 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);
}
/// Draw the buffer
#[cfg(feature = "swash")]
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
self.inner.draw(self.font_system, cache, color, f);
}
}

View file

@ -167,7 +167,7 @@ impl BufferLine {
} }
/// Shape line, will cache results /// Shape line, will cache results
pub fn shape(&mut self, font_system: &FontSystem) -> &ShapeLine { pub fn shape(&mut self, font_system: &mut FontSystem) -> &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_list)); self.shape_opt = Some(ShapeLine::new(font_system, &self.text, &self.attrs_list));
self.layout_opt = None; self.layout_opt = None;
@ -183,7 +183,7 @@ impl BufferLine {
/// Layout line, will cache results /// Layout line, will cache results
pub fn layout( pub fn layout(
&mut self, &mut self,
font_system: &FontSystem, font_system: &mut FontSystem,
font_size: f32, font_size: f32,
width: f32, width: f32,
wrap: Wrap, wrap: Wrap,

View file

@ -10,20 +10,22 @@ use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
use crate::Color; use crate::Color;
use crate::{Action, Affinity, AttrsList, Buffer, BufferLine, Cursor, Edit, LayoutCursor}; use crate::{
Action, Affinity, AttrsList, Buffer, BufferLine, Cursor, Edit, FontSystem, LayoutCursor,
};
/// A wrapper of [`Buffer`] for easy editing /// A wrapper of [`Buffer`] for easy editing
pub struct Editor<'a> { pub struct Editor {
buffer: Buffer<'a>, buffer: Buffer,
cursor: Cursor, cursor: Cursor,
cursor_x_opt: Option<i32>, cursor_x_opt: Option<i32>,
select_opt: Option<Cursor>, select_opt: Option<Cursor>,
cursor_moved: bool, cursor_moved: bool,
} }
impl<'a> Editor<'a> { impl Editor {
/// Create a new [`Editor`] with the provided [`Buffer`] /// Create a new [`Editor`] with the provided [`Buffer`]
pub fn new(buffer: Buffer<'a>) -> Self { pub fn new(buffer: Buffer) -> Self {
Self { Self {
buffer, buffer,
cursor: Cursor::default(), cursor: Cursor::default(),
@ -33,10 +35,10 @@ impl<'a> Editor<'a> {
} }
} }
fn set_layout_cursor(&mut self, cursor: LayoutCursor) { fn set_layout_cursor(&mut self, font_system: &mut FontSystem, cursor: LayoutCursor) {
let layout = self let layout = self
.buffer .buffer
.line_layout(cursor.line) .line_layout(font_system, cursor.line)
.expect("layout not found"); .expect("layout not found");
let layout_line = match layout.get(cursor.layout) { let layout_line = match layout.get(cursor.layout) {
@ -68,12 +70,12 @@ impl<'a> Editor<'a> {
} }
} }
impl<'a> Edit<'a> for Editor<'a> { impl Edit for Editor {
fn buffer(&self) -> &Buffer<'a> { fn buffer(&self) -> &Buffer {
&self.buffer &self.buffer
} }
fn buffer_mut(&mut self) -> &mut Buffer<'a> { fn buffer_mut(&mut self) -> &mut Buffer {
&mut self.buffer &mut self.buffer
} }
@ -92,12 +94,12 @@ impl<'a> Edit<'a> for Editor<'a> {
} }
} }
fn shape_as_needed(&mut self) { fn shape_as_needed(&mut self, font_system: &mut FontSystem) {
if self.cursor_moved { if self.cursor_moved {
self.buffer.shape_until_cursor(self.cursor); self.buffer.shape_until_cursor(font_system, self.cursor);
self.cursor_moved = false; self.cursor_moved = false;
} else { } else {
self.buffer.shape_until_scroll(); self.buffer.shape_until_scroll(font_system);
} }
} }
@ -279,7 +281,7 @@ impl<'a> Edit<'a> for Editor<'a> {
self.cursor.index = self.buffer.lines[self.cursor.line].text().len() - after_len; self.cursor.index = self.buffer.lines[self.cursor.line].text().len() - after_len;
} }
fn action(&mut self, action: Action) { fn action(&mut self, font_system: &mut FontSystem, action: Action) {
let old_cursor = self.cursor; let old_cursor = self.cursor;
match action { match action {
@ -333,9 +335,9 @@ impl<'a> Edit<'a> for Editor<'a> {
.map(|shape| shape.rtl); .map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt { if let Some(rtl) = rtl_opt {
if rtl { if rtl {
self.action(Action::Next); self.action(font_system, Action::Next);
} else { } else {
self.action(Action::Previous); self.action(font_system, Action::Previous);
} }
} }
} }
@ -346,9 +348,9 @@ impl<'a> Edit<'a> for Editor<'a> {
.map(|shape| shape.rtl); .map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt { if let Some(rtl) = rtl_opt {
if rtl { if rtl {
self.action(Action::Previous); self.action(font_system, Action::Previous);
} else { } else {
self.action(Action::Next); self.action(font_system, Action::Next);
} }
} }
} }
@ -373,7 +375,7 @@ impl<'a> Edit<'a> for Editor<'a> {
cursor.glyph = cursor_x as usize; //TODO: glyph x position cursor.glyph = cursor_x as usize; //TODO: glyph x position
} }
self.set_layout_cursor(cursor); self.set_layout_cursor(font_system, cursor);
} }
Action::Down => { Action::Down => {
//TODO: make this preserve X as best as possible! //TODO: make this preserve X as best as possible!
@ -381,7 +383,7 @@ impl<'a> Edit<'a> for Editor<'a> {
let layout_len = self let layout_len = self
.buffer .buffer
.line_layout(cursor.line) .line_layout(font_system, cursor.line)
.expect("layout not found") .expect("layout not found")
.len(); .len();
@ -402,18 +404,18 @@ impl<'a> Edit<'a> for Editor<'a> {
cursor.glyph = cursor_x as usize; //TODO: glyph x position cursor.glyph = cursor_x as usize; //TODO: glyph x position
} }
self.set_layout_cursor(cursor); self.set_layout_cursor(font_system, cursor);
} }
Action::Home => { Action::Home => {
let mut cursor = self.buffer.layout_cursor(&self.cursor); let mut cursor = self.buffer.layout_cursor(&self.cursor);
cursor.glyph = 0; cursor.glyph = 0;
self.set_layout_cursor(cursor); self.set_layout_cursor(font_system, cursor);
self.cursor_x_opt = None; self.cursor_x_opt = None;
} }
Action::End => { Action::End => {
let mut cursor = self.buffer.layout_cursor(&self.cursor); let mut cursor = self.buffer.layout_cursor(&self.cursor);
cursor.glyph = usize::max_value(); cursor.glyph = usize::max_value();
self.set_layout_cursor(cursor); self.set_layout_cursor(font_system, cursor);
self.cursor_x_opt = None; self.cursor_x_opt = None;
} }
Action::ParagraphStart => { Action::ParagraphStart => {
@ -427,10 +429,10 @@ impl<'a> Edit<'a> for Editor<'a> {
self.buffer.set_redraw(true); self.buffer.set_redraw(true);
} }
Action::PageUp => { Action::PageUp => {
self.action(Action::Vertical(-self.buffer.size().1 as i32)); self.action(font_system, Action::Vertical(-self.buffer.size().1 as i32));
} }
Action::PageDown => { Action::PageDown => {
self.action(Action::Vertical(self.buffer.size().1 as i32)); self.action(font_system, Action::Vertical(self.buffer.size().1 as i32));
} }
Action::Vertical(px) => { Action::Vertical(px) => {
// TODO more efficient // TODO more efficient
@ -438,12 +440,12 @@ impl<'a> Edit<'a> for Editor<'a> {
match lines.cmp(&0) { match lines.cmp(&0) {
Ordering::Less => { Ordering::Less => {
for _ in 0..-lines { for _ in 0..-lines {
self.action(Action::Up); self.action(font_system, Action::Up);
} }
} }
Ordering::Greater => { Ordering::Greater => {
for _ in 0..lines { for _ in 0..lines {
self.action(Action::Down); self.action(font_system, Action::Down);
} }
} }
Ordering::Equal => {} Ordering::Equal => {}
@ -459,7 +461,7 @@ impl<'a> Edit<'a> for Editor<'a> {
// Filter out special chars (except for tab), use Action instead // Filter out special chars (except for tab), use Action instead
log::debug!("Refusing to insert control character {:?}", character); log::debug!("Refusing to insert control character {:?}", character);
} else if character == '\n' { } else if character == '\n' {
self.action(Action::Enter); self.action(font_system, Action::Enter);
} else { } else {
let mut str_buf = [0u8; 8]; let mut str_buf = [0u8; 8];
let str_ref = character.encode_utf8(&mut str_buf); let str_ref = character.encode_utf8(&mut str_buf);
@ -619,9 +621,9 @@ impl<'a> Edit<'a> for Editor<'a> {
.map(|shape| shape.rtl); .map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt { if let Some(rtl) = rtl_opt {
if rtl { if rtl {
self.action(Action::NextWord); self.action(font_system, Action::NextWord);
} else { } else {
self.action(Action::PreviousWord); self.action(font_system, Action::PreviousWord);
} }
} }
} }
@ -632,9 +634,9 @@ impl<'a> Edit<'a> for Editor<'a> {
.map(|shape| shape.rtl); .map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt { if let Some(rtl) = rtl_opt {
if rtl { if rtl {
self.action(Action::PreviousWord); self.action(font_system, Action::PreviousWord);
} else { } else {
self.action(Action::NextWord); self.action(font_system, Action::NextWord);
} }
} }
} }
@ -673,8 +675,13 @@ impl<'a> Edit<'a> for Editor<'a> {
/// Draw the editor /// Draw the editor
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F) fn draw<F>(
where &self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color), F: FnMut(i32, i32, u32, u32, Color),
{ {
let font_size = self.buffer.metrics().font_size; let font_size = self.buffer.metrics().font_size;
@ -833,14 +840,9 @@ impl<'a> Edit<'a> for Editor<'a> {
None => color, None => color,
}; };
cache.with_pixels( cache.with_pixels(font_system, cache_key, glyph_color, |x, y, color| {
self.buffer.font_system(), f(x_int + x, line_y as i32 + y_int + y, 1, 1, color);
cache_key, });
glyph_color,
|x, y, color| {
f(x_int + x, line_y as i32 + y_int + y, 1, 1, color);
},
);
} }
} }
} }

View file

@ -3,7 +3,7 @@ use alloc::string::String;
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
use crate::Color; use crate::Color;
use crate::{AttrsList, Buffer, Cursor}; use crate::{AttrsList, BorrowedWithFontSystem, Buffer, Cursor, FontSystem};
pub use self::editor::*; pub use self::editor::*;
mod editor; mod editor;
@ -78,12 +78,26 @@ pub enum Action {
} }
/// A trait to allow easy replacements of [`Editor`], like `SyntaxEditor` /// A trait to allow easy replacements of [`Editor`], like `SyntaxEditor`
pub trait Edit<'a> { pub trait Edit {
/// Mutably borrows `self` together with an [`FontSystem`] for more convenient methods
fn borrow_with<'a>(
&'a mut self,
font_system: &'a mut FontSystem,
) -> BorrowedWithFontSystem<'a, Self>
where
Self: Sized,
{
BorrowedWithFontSystem {
inner: self,
font_system,
}
}
/// Get the internal [`Buffer`] /// Get the internal [`Buffer`]
fn buffer(&self) -> &Buffer<'a>; fn buffer(&self) -> &Buffer;
/// Get the internal [`Buffer`], mutably /// Get the internal [`Buffer`], mutably
fn buffer_mut(&mut self) -> &mut Buffer<'a>; fn buffer_mut(&mut self) -> &mut Buffer;
/// Get the current cursor position /// Get the current cursor position
fn cursor(&self) -> Cursor; fn cursor(&self) -> Cursor;
@ -95,7 +109,7 @@ pub trait Edit<'a> {
fn set_select_opt(&mut self, select_opt: Option<Cursor>); fn set_select_opt(&mut self, select_opt: Option<Cursor>);
/// Shape lines until scroll, after adjusting scroll if the cursor moved /// Shape lines until scroll, after adjusting scroll if the cursor moved
fn shape_as_needed(&mut self); fn shape_as_needed(&mut self, font_system: &mut FontSystem);
/// Copy selection /// Copy selection
fn copy_selection(&mut self) -> Option<String>; fn copy_selection(&mut self) -> Option<String>;
@ -109,11 +123,45 @@ pub trait Edit<'a> {
fn insert_string(&mut self, data: &str, attrs_list: Option<AttrsList>); fn insert_string(&mut self, data: &str, attrs_list: Option<AttrsList>);
/// Perform an [Action] on the editor /// Perform an [Action] on the editor
fn action(&mut self, action: Action); fn action(&mut self, font_system: &mut FontSystem, action: Action);
/// Draw the editor /// Draw the editor
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, f: F) fn draw<F>(
where &self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
f: F,
) where
F: FnMut(i32, i32, u32, u32, Color); F: FnMut(i32, i32, u32, u32, Color);
} }
impl<'a, T: Edit> BorrowedWithFontSystem<'a, T> {
/// Get the internal [`Buffer`], mutably
pub fn buffer_mut(&mut self) -> BorrowedWithFontSystem<Buffer> {
BorrowedWithFontSystem {
inner: self.inner.buffer_mut(),
font_system: self.font_system,
}
}
/// Shape lines until scroll, after adjusting scroll if the cursor moved
pub fn shape_as_needed(&mut self) {
self.inner.shape_as_needed(self.font_system);
}
/// Perform an [Action] on the editor
pub fn action(&mut self, action: Action) {
self.inner.action(self.font_system, action);
}
/// Draw the editor
#[cfg(feature = "swash")]
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
self.inner.draw(self.font_system, cache, color, f);
}
}

View file

@ -7,7 +7,10 @@ use syntect::highlighting::{
}; };
use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet}; use syntect::parsing::{ParseState, ScopeStack, SyntaxReference, SyntaxSet};
use crate::{Action, AttrsList, Buffer, Color, Cursor, Edit, Editor, Style, Weight, Wrap}; use crate::{
Action, AttrsList, BorrowedWithFontSystem, Buffer, Color, Cursor, Edit, Editor, FontSystem,
Style, Weight, Wrap,
};
pub struct SyntaxSystem { pub struct SyntaxSystem {
pub syntax_set: SyntaxSet, pub syntax_set: SyntaxSet,
@ -27,7 +30,7 @@ impl SyntaxSystem {
/// A wrapper of [`Editor`] with syntax highlighting provided by [`SyntaxSystem`] /// A wrapper of [`Editor`] with syntax highlighting provided by [`SyntaxSystem`]
pub struct SyntaxEditor<'a> { pub struct SyntaxEditor<'a> {
editor: Editor<'a>, editor: Editor,
syntax_system: &'a SyntaxSystem, syntax_system: &'a SyntaxSystem,
syntax: &'a SyntaxReference, syntax: &'a SyntaxReference,
theme: &'a Theme, theme: &'a Theme,
@ -41,11 +44,7 @@ impl<'a> SyntaxEditor<'a> {
/// A good default theme name is "base16-eighties.dark". /// A good default theme name is "base16-eighties.dark".
/// ///
/// Returns None if theme not found /// Returns None if theme not found
pub fn new( pub fn new(buffer: Buffer, syntax_system: &'a SyntaxSystem, theme_name: &str) -> Option<Self> {
buffer: Buffer<'a>,
syntax_system: &'a SyntaxSystem,
theme_name: &str,
) -> Option<Self> {
let editor = Editor::new(buffer); let editor = Editor::new(buffer);
let syntax = syntax_system.syntax_set.find_syntax_plain_text(); let syntax = syntax_system.syntax_set.find_syntax_plain_text();
let theme = syntax_system.theme_set.themes.get(theme_name)?; let theme = syntax_system.theme_set.themes.get(theme_name)?;
@ -69,13 +68,14 @@ impl<'a> SyntaxEditor<'a> {
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn load_text<P: AsRef<Path>>( pub fn load_text<P: AsRef<Path>>(
&mut self, &mut self,
font_system: &mut FontSystem,
path: P, path: P,
attrs: crate::Attrs<'a>, attrs: crate::Attrs,
) -> io::Result<()> { ) -> io::Result<()> {
let path = path.as_ref(); let path = path.as_ref();
let text = fs::read_to_string(path)?; let text = fs::read_to_string(path)?;
self.editor.buffer_mut().set_text(&text, attrs); self.editor.buffer_mut().set_text(font_system, &text, attrs);
//TODO: re-use text //TODO: re-use text
self.syntax = match self.syntax_system.syntax_set.find_syntax_for_file(path) { self.syntax = match self.syntax_system.syntax_set.find_syntax_for_file(path) {
@ -115,12 +115,12 @@ impl<'a> SyntaxEditor<'a> {
} }
} }
impl<'a> Edit<'a> for SyntaxEditor<'a> { impl<'a> Edit for SyntaxEditor<'a> {
fn buffer(&self) -> &Buffer<'a> { fn buffer(&self) -> &Buffer {
self.editor.buffer() self.editor.buffer()
} }
fn buffer_mut(&mut self) -> &mut Buffer<'a> { fn buffer_mut(&mut self) -> &mut Buffer {
self.editor.buffer_mut() self.editor.buffer_mut()
} }
@ -136,7 +136,7 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
self.editor.set_select_opt(select_opt); self.editor.set_select_opt(select_opt);
} }
fn shape_as_needed(&mut self) { fn shape_as_needed(&mut self, font_system: &mut FontSystem) {
#[cfg(feature = "std")] #[cfg(feature = "std")]
let now = std::time::Instant::now(); let now = std::time::Instant::now();
@ -201,7 +201,7 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
line.set_wrap(Wrap::Word); line.set_wrap(Wrap::Word);
//TODO: efficiently do syntax highlighting without having to shape whole buffer //TODO: efficiently do syntax highlighting without having to shape whole buffer
buffer.line_shape(line_i); buffer.line_shape(font_system, line_i);
let cache_item = (parse_state.clone(), highlight_state.clone()); let cache_item = (parse_state.clone(), highlight_state.clone());
if line_i < self.syntax_cache.len() { if line_i < self.syntax_cache.len() {
@ -226,7 +226,7 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
); );
} }
self.editor.shape_as_needed(); self.editor.shape_as_needed(font_system);
} }
fn copy_selection(&mut self) -> Option<String> { fn copy_selection(&mut self) -> Option<String> {
@ -241,18 +241,36 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
self.editor.insert_string(data, attrs_list); self.editor.insert_string(data, attrs_list);
} }
fn action(&mut self, action: Action) { fn action(&mut self, font_system: &mut FontSystem, action: Action) {
self.editor.action(action); self.editor.action(font_system, action);
} }
/// Draw the editor /// Draw the editor
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, _color: Color, mut f: F) fn draw<F>(
where &self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
_color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color), F: FnMut(i32, i32, u32, u32, Color),
{ {
let size = self.buffer().size(); let size = self.buffer().size();
f(0, 0, size.0 as u32, size.1 as u32, self.background_color()); f(0, 0, size.0 as u32, size.1 as u32, self.background_color());
self.editor.draw(cache, self.foreground_color(), f); self.editor
.draw(font_system, cache, self.foreground_color(), f);
}
}
impl<'a, 'b> BorrowedWithFontSystem<'b, SyntaxEditor<'a>> {
/// Load text from a file, and also set syntax to the best option
///
/// ## Errors
///
/// Returns an [`io::Error`] if reading the file fails
#[cfg(feature = "std")]
pub fn load_text<P: AsRef<Path>>(&mut self, path: P, attrs: crate::Attrs) -> io::Result<()> {
self.inner.load_text(self.font_system, path, attrs)
} }
} }

View file

@ -2,7 +2,10 @@ use alloc::string::String;
use core::cmp; use core::cmp;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use crate::{Action, AttrsList, Buffer, Color, Cursor, Edit, SyntaxEditor}; use crate::{
Action, AttrsList, BorrowedWithFontSystem, Buffer, Color, Cursor, Edit, FontSystem,
SyntaxEditor,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Mode { enum Mode {
@ -30,10 +33,11 @@ impl<'a> ViEditor<'a> {
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub fn load_text<P: AsRef<std::path::Path>>( pub fn load_text<P: AsRef<std::path::Path>>(
&mut self, &mut self,
font_system: &mut FontSystem,
path: P, path: P,
attrs: crate::Attrs<'a>, attrs: crate::Attrs,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
self.editor.load_text(path, attrs) self.editor.load_text(font_system, path, attrs)
} }
/// Get the default background color /// Get the default background color
@ -47,12 +51,12 @@ impl<'a> ViEditor<'a> {
} }
} }
impl<'a> Edit<'a> for ViEditor<'a> { impl<'a> Edit for ViEditor<'a> {
fn buffer(&self) -> &Buffer<'a> { fn buffer(&self) -> &Buffer {
self.editor.buffer() self.editor.buffer()
} }
fn buffer_mut(&mut self) -> &mut Buffer<'a> { fn buffer_mut(&mut self) -> &mut Buffer {
self.editor.buffer_mut() self.editor.buffer_mut()
} }
@ -68,8 +72,8 @@ impl<'a> Edit<'a> for ViEditor<'a> {
self.editor.set_select_opt(select_opt); self.editor.set_select_opt(select_opt);
} }
fn shape_as_needed(&mut self) { fn shape_as_needed(&mut self, font_system: &mut FontSystem) {
self.editor.shape_as_needed() self.editor.shape_as_needed(font_system);
} }
fn copy_selection(&mut self) -> Option<String> { fn copy_selection(&mut self) -> Option<String> {
@ -84,7 +88,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
self.editor.insert_string(data, attrs_list); self.editor.insert_string(data, attrs_list);
} }
fn action(&mut self, action: Action) { fn action(&mut self, font_system: &mut FontSystem, action: Action) {
let old_mode = self.mode; let old_mode = self.mode;
match self.mode { match self.mode {
@ -92,18 +96,18 @@ impl<'a> Edit<'a> for ViEditor<'a> {
Action::Insert(c) => match c { Action::Insert(c) => match c {
// Enter insert mode after cursor // Enter insert mode after cursor
'a' => { 'a' => {
self.editor.action(Action::Right); self.editor.action(font_system, Action::Right);
self.mode = Mode::Insert; self.mode = Mode::Insert;
} }
// Enter insert mode at end of line // Enter insert mode at end of line
'A' => { 'A' => {
self.editor.action(Action::End); self.editor.action(font_system, Action::End);
self.mode = Mode::Insert; self.mode = Mode::Insert;
} }
// Change mode // Change mode
'c' => { 'c' => {
if self.editor.select_opt().is_some() { if self.editor.select_opt().is_some() {
self.editor.action(Action::Delete); self.editor.action(font_system, Action::Delete);
self.mode = Mode::Insert; self.mode = Mode::Insert;
} else { } else {
//TODO: change to next cursor movement //TODO: change to next cursor movement
@ -112,7 +116,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
// Delete mode // Delete mode
'd' => { 'd' => {
if self.editor.select_opt().is_some() { if self.editor.select_opt().is_some() {
self.editor.action(Action::Delete); self.editor.action(font_system, Action::Delete);
} else { } else {
//TODO: delete to next cursor movement //TODO: delete to next cursor movement
} }
@ -124,33 +128,33 @@ impl<'a> Edit<'a> for ViEditor<'a> {
// Enter insert mode at start of line // Enter insert mode at start of line
'I' => { 'I' => {
//TODO: soft home, skip whitespace //TODO: soft home, skip whitespace
self.editor.action(Action::Home); self.editor.action(font_system, Action::Home);
self.mode = Mode::Insert; self.mode = Mode::Insert;
} }
// Create line after and enter insert mode // Create line after and enter insert mode
'o' => { 'o' => {
self.editor.action(Action::End); self.editor.action(font_system, Action::End);
self.editor.action(Action::Enter); self.editor.action(font_system, Action::Enter);
self.mode = Mode::Insert; self.mode = Mode::Insert;
} }
// Create line before and enter insert mode // Create line before and enter insert mode
'O' => { 'O' => {
self.editor.action(Action::Home); self.editor.action(font_system, Action::Home);
self.editor.action(Action::Enter); self.editor.action(font_system, Action::Enter);
self.editor.shape_as_needed(); // TODO: do not require this? self.editor.shape_as_needed(font_system); // TODO: do not require this?
self.editor.action(Action::Up); self.editor.action(font_system, Action::Up);
self.mode = Mode::Insert; self.mode = Mode::Insert;
} }
// Left // Left
'h' => self.editor.action(Action::Left), 'h' => self.editor.action(font_system, Action::Left),
// Top of screen // Top of screen
//TODO: 'H' => self.editor.action(Action::ScreenHigh), //TODO: 'H' => self.editor.action(Action::ScreenHigh),
// Down // Down
'j' => self.editor.action(Action::Down), 'j' => self.editor.action(font_system, Action::Down),
// Up // Up
'k' => self.editor.action(Action::Up), 'k' => self.editor.action(font_system, Action::Up),
// Right // Right
'l' => self.editor.action(Action::Right), 'l' => self.editor.action(font_system, Action::Right),
// Bottom of screen // Bottom of screen
//TODO: 'L' => self.editor.action(Action::ScreenLow), //TODO: 'L' => self.editor.action(Action::ScreenLow),
// Middle of screen // Middle of screen
@ -168,23 +172,23 @@ impl<'a> Edit<'a> for ViEditor<'a> {
if self.editor.select_opt().is_some() { if self.editor.select_opt().is_some() {
self.editor.set_select_opt(None); self.editor.set_select_opt(None);
} else { } else {
self.editor.action(Action::Home); self.editor.action(font_system, Action::Home);
self.editor.set_select_opt(Some(self.editor.cursor())); self.editor.set_select_opt(Some(self.editor.cursor()));
//TODO: set cursor_x_opt to max //TODO: set cursor_x_opt to max
self.editor.action(Action::End); self.editor.action(font_system, Action::End);
} }
} }
// Remove character at cursor // Remove character at cursor
'x' => self.editor.action(Action::Delete), 'x' => self.editor.action(font_system, Action::Delete),
// Remove character before cursor // Remove character before cursor
'X' => self.editor.action(Action::Backspace), 'X' => self.editor.action(font_system, Action::Backspace),
// Go to start of line // Go to start of line
'0' => self.editor.action(Action::Home), '0' => self.editor.action(font_system, Action::Home),
// Go to end of line // Go to end of line
'$' => self.editor.action(Action::End), '$' => self.editor.action(font_system, Action::End),
// Go to start of line after whitespace // Go to start of line after whitespace
//TODO: implement this //TODO: implement this
'^' => self.editor.action(Action::Home), '^' => self.editor.action(font_system, Action::Home),
// Enter command mode // Enter command mode
':' => { ':' => {
self.mode = Mode::Command; self.mode = Mode::Command;
@ -199,18 +203,18 @@ impl<'a> Edit<'a> for ViEditor<'a> {
} }
_ => (), _ => (),
}, },
_ => self.editor.action(action), _ => self.editor.action(font_system, action),
}, },
Mode::Insert => match action { Mode::Insert => match action {
Action::Escape => { Action::Escape => {
let cursor = self.cursor(); let cursor = self.cursor();
let layout_cursor = self.buffer().layout_cursor(&cursor); let layout_cursor = self.buffer().layout_cursor(&cursor);
if layout_cursor.glyph > 0 { if layout_cursor.glyph > 0 {
self.editor.action(Action::Left); self.editor.action(font_system, Action::Left);
} }
self.mode = Mode::Normal; self.mode = Mode::Normal;
} }
_ => self.editor.action(action), _ => self.editor.action(font_system, action),
}, },
_ => { _ => {
//TODO: other modes //TODO: other modes
@ -224,8 +228,13 @@ impl<'a> Edit<'a> for ViEditor<'a> {
} }
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F) fn draw<F>(
where &self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color), F: FnMut(i32, i32, u32, u32, Color),
{ {
let font_size = self.buffer().metrics().font_size; let font_size = self.buffer().metrics().font_size;
@ -421,15 +430,22 @@ impl<'a> Edit<'a> for ViEditor<'a> {
None => color, None => color,
}; };
cache.with_pixels( cache.with_pixels(font_system, cache_key, glyph_color, |x, y, color| {
self.buffer().font_system(), f(x_int + x, line_y as i32 + y_int + y, 1, 1, color);
cache_key, });
glyph_color,
|x, y, color| {
f(x_int + x, line_y as i32 + y_int + y, 1, 1, color);
},
);
} }
} }
} }
} }
impl<'a, 'b> BorrowedWithFontSystem<'b, ViEditor<'a>> {
/// Load text from a file, and also set syntax to the best option
#[cfg(feature = "std")]
pub fn load_text<P: AsRef<std::path::Path>>(
&mut self,
path: P,
attrs: crate::Attrs,
) -> std::io::Result<()> {
self.inner.load_text(self.font_system, path, attrs)
}
}

View file

@ -3,9 +3,10 @@
use alloc::sync::Arc; use alloc::sync::Arc;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
use alloc::vec::Vec; use alloc::vec::Vec;
use fontdb::Family;
use unicode_script::Script; use unicode_script::Script;
use crate::Font; use crate::{Font, FontSystem};
use self::platform::*; use self::platform::*;
@ -26,11 +27,11 @@ mod platform;
mod platform; mod platform;
pub struct FontFallbackIter<'a> { pub struct FontFallbackIter<'a> {
fonts: &'a [Arc<Font<'a>>], font_system: &'a mut FontSystem,
default_families: &'a [&'a str], font_ids: &'a [fontdb::ID],
default_families: &'a [&'a Family<'a>],
default_i: usize, default_i: usize,
scripts: Vec<Script>, scripts: Vec<Script>,
locale: &'a str,
script_i: (usize, usize), script_i: (usize, usize),
common_i: usize, common_i: usize,
other_i: usize, other_i: usize,
@ -39,17 +40,17 @@ pub struct FontFallbackIter<'a> {
impl<'a> FontFallbackIter<'a> { impl<'a> FontFallbackIter<'a> {
pub fn new( pub fn new(
fonts: &'a [Arc<Font<'a>>], font_system: &'a mut FontSystem,
default_families: &'a [&'a str], font_ids: &'a [fontdb::ID],
default_families: &'a [&'a Family<'a>],
scripts: Vec<Script>, scripts: Vec<Script>,
locale: &'a str,
) -> Self { ) -> Self {
Self { Self {
fonts, font_system,
font_ids,
default_families, default_families,
default_i: 0, default_i: 0,
scripts, scripts,
locale,
script_i: (0, 0), script_i: (0, 0),
common_i: 0, common_i: 0,
other_i: 0, other_i: 0,
@ -57,21 +58,20 @@ impl<'a> FontFallbackIter<'a> {
} }
} }
pub fn check_missing(&self, word: &str) { pub fn check_missing(&mut self, word: &str) {
if self.end { if self.end {
log::debug!( log::debug!(
"Failed to find any fallback for {:?} locale '{}': '{}'", "Failed to find any fallback for {:?} locale '{}': '{}'",
self.scripts, self.scripts,
self.locale, self.font_system.locale(),
word word
); );
} else if self.other_i > 0 { } else if self.other_i > 0 {
let font = &self.fonts[self.other_i - 1];
log::debug!( log::debug!(
"Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'", "Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'",
self.scripts, self.scripts,
self.locale, self.font_system.locale(),
font.name(), self.face_name(self.font_ids[self.other_i - 1]),
word word
); );
} else if !self.scripts.is_empty() && self.common_i > 0 { } else if !self.scripts.is_empty() && self.common_i > 0 {
@ -79,24 +79,48 @@ impl<'a> FontFallbackIter<'a> {
log::debug!( log::debug!(
"Failed to find script fallback for {:?} locale '{}', used '{}': '{}'", "Failed to find script fallback for {:?} locale '{}', used '{}': '{}'",
self.scripts, self.scripts,
self.locale, self.font_system.locale(),
family, family,
word word
); );
} }
} }
pub fn face_name(&self, id: fontdb::ID) -> &str {
if let Some(face) = self.font_system.db().face(id) {
if let Some((name, _)) = face.families.first() {
name
} else {
&face.post_script_name
}
} else {
"invalid font id"
}
}
fn face_contains_family(&self, id: fontdb::ID, family_name: &str) -> bool {
if let Some(face) = self.font_system.db().face(id) {
face.families.iter().any(|(name, _)| name == family_name)
} else {
false
}
}
} }
impl<'a> Iterator for FontFallbackIter<'a> { impl<'a> Iterator for FontFallbackIter<'a> {
type Item = &'a Arc<Font<'a>>; type Item = Arc<Font>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
while self.default_i < self.default_families.len() { while self.default_i < self.default_families.len() {
let default_family = self.default_families[self.default_i];
self.default_i += 1; self.default_i += 1;
for id in self.font_ids.iter() {
for font in self.fonts.iter() { let default_family = self
if font.contains_family(default_family) { .font_system
return Some(font); .db()
.family_name(self.default_families[self.default_i - 1]);
if self.face_contains_family(*id, default_family) {
if let Some(font) = self.font_system.get_font(*id) {
return Some(font);
}
} }
} }
} }
@ -104,20 +128,22 @@ impl<'a> Iterator for FontFallbackIter<'a> {
while self.script_i.0 < self.scripts.len() { while self.script_i.0 < self.scripts.len() {
let script = self.scripts[self.script_i.0]; let script = self.scripts[self.script_i.0];
let script_families = script_fallback(script, self.locale); let script_families = script_fallback(script, self.font_system.locale());
while self.script_i.1 < script_families.len() { while self.script_i.1 < script_families.len() {
let script_family = script_families[self.script_i.1]; let script_family = script_families[self.script_i.1];
self.script_i.1 += 1; self.script_i.1 += 1;
for font in self.fonts.iter() { for id in self.font_ids.iter() {
if font.contains_family(script_family) { if self.face_contains_family(*id, script_family) {
return Some(font); if let Some(font) = self.font_system.get_font(*id) {
return Some(font);
}
} }
} }
log::debug!( log::debug!(
"failed to find family '{}' for script {:?} and locale '{}'", "failed to find family '{}' for script {:?} and locale '{}'",
script_family, script_family,
script, script,
self.locale self.font_system.locale(),
); );
} }
@ -129,9 +155,11 @@ impl<'a> Iterator for FontFallbackIter<'a> {
while self.common_i < common_families.len() { while self.common_i < common_families.len() {
let common_family = common_families[self.common_i]; let common_family = common_families[self.common_i];
self.common_i += 1; self.common_i += 1;
for font in self.fonts.iter() { for id in self.font_ids.iter() {
if font.contains_family(common_family) { if self.face_contains_family(*id, common_family) {
return Some(font); if let Some(font) = self.font_system.get_font(*id) {
return Some(font);
}
} }
} }
log::debug!("failed to find family '{}'", common_family); log::debug!("failed to find family '{}'", common_family);
@ -140,14 +168,16 @@ impl<'a> Iterator for FontFallbackIter<'a> {
//TODO: do we need to do this? //TODO: do we need to do this?
//TODO: do not evaluate fonts more than once! //TODO: do not evaluate fonts more than once!
let forbidden_families = forbidden_fallback(); let forbidden_families = forbidden_fallback();
while self.other_i < self.fonts.len() { while self.other_i < self.font_ids.len() {
let font = &self.fonts[self.other_i]; let id = self.font_ids[self.other_i];
self.other_i += 1; self.other_i += 1;
if forbidden_families if forbidden_families
.iter() .iter()
.all(|family| !font.contains_family(family)) .all(|family_name| !self.face_contains_family(id, family_name))
{ {
return Some(font); if let Some(font) = self.font_system.get_font(id) {
return Some(font);
}
} }
} }

View file

@ -1,14 +0,0 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use alloc::sync::Arc;
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use crate::Font;
/// Fonts that match a pattern
pub struct FontMatches<'a> {
pub locale: &'a str,
pub default_family: String,
pub fonts: Vec<Arc<Font<'a>>>,
}

View file

@ -1,67 +1,95 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use core::ops::Deref;
pub(crate) mod fallback; pub(crate) mod fallback;
pub use self::matches::*; use alloc::sync::Arc;
mod matches;
pub use self::system::*; pub use self::system::*;
mod system; mod system;
/// A font /// A font
pub struct Font<'a> { pub struct Font(FontInner);
pub info: &'a fontdb::FaceInfo,
pub data: &'a [u8], #[ouroboros::self_referencing]
pub rustybuzz: rustybuzz::Face<'a>, #[allow(dead_code)]
#[cfg(feature = "swash")] struct FontInner {
pub swash: (u32, swash::CacheKey), id: fontdb::ID,
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
#[borrows(data)]
#[covariant]
rustybuzz: rustybuzz::Face<'this>,
// workaround, since ouroboros does not work with #[cfg(feature = "swash")]
swash: SwashKey,
} }
impl<'a> Font<'a> { #[cfg(feature = "swash")]
pub fn new(info: &'a fontdb::FaceInfo) -> Option<Self> { pub type SwashKey = (u32, swash::CacheKey);
#[cfg(not(feature = "swash"))]
pub type SwashKey = ();
impl Font {
pub fn new(info: &fontdb::FaceInfo) -> Option<Self> {
#[allow(unused_variables)]
let data = match &info.source { let data = match &info.source {
fontdb::Source::Binary(data) => data.deref().as_ref(), fontdb::Source::Binary(data) => Arc::clone(data),
#[cfg(feature = "std")] #[cfg(feature = "std")]
fontdb::Source::File(path) => { fontdb::Source::File(path) => {
log::warn!("Unsupported fontdb Source::File('{}')", path.display()); log::warn!("Unsupported fontdb Source::File('{}')", path.display());
return None; return None;
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
fontdb::Source::SharedFile(_path, data) => data.deref().as_ref(), fontdb::Source::SharedFile(_path, data) => Arc::clone(data),
}; };
Some(Self(
Some(Self { FontInnerTryBuilder {
info, id: info.id,
data, swash: {
rustybuzz: rustybuzz::Face::from_slice(data, info.index)?, #[cfg(feature = "swash")]
#[cfg(feature = "swash")] let swash = {
swash: { let swash =
let swash = swash::FontRef::from_index(data, info.index as usize)?; swash::FontRef::from_index((*data).as_ref(), info.index as usize)?;
(swash.offset, swash.key) (swash.offset, swash.key)
}, };
}) #[cfg(not(feature = "swash"))]
let swash = ();
swash
},
data,
rustybuzz_builder: |data| {
rustybuzz::Face::from_slice((**data).as_ref(), info.index).ok_or(())
},
}
.try_build()
.ok()?,
))
} }
pub fn name(&self) -> &str { pub fn id(&self) -> fontdb::ID {
if let Some((name, _)) = self.info.families.first() { *self.0.borrow_id()
name
} else {
&self.info.post_script_name
}
} }
pub fn contains_family(&self, family: &str) -> bool { pub fn data(&self) -> &[u8] {
self.info.families.iter().any(|(name, _)| name == family) (**self.0.borrow_data()).as_ref()
}
pub fn rustybuzz(&self) -> &rustybuzz::Face {
self.0.borrow_rustybuzz()
} }
#[cfg(feature = "swash")] #[cfg(feature = "swash")]
pub fn as_swash(&self) -> swash::FontRef { pub fn as_swash(&self) -> swash::FontRef {
let swash = self.0.borrow_swash();
swash::FontRef { swash::FontRef {
data: self.data, data: self.data(),
offset: self.swash.0, offset: swash.0,
key: self.swash.1, key: swash.1,
} }
} }
// This is used to prevent warnings due to the swash field being unused.
#[cfg(not(feature = "swash"))]
#[allow(dead_code)]
fn as_swash(&self) {
self.0.borrow_swash();
}
} }

View file

@ -1,3 +1,5 @@
use core::ops::{Deref, DerefMut};
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
pub use self::no_std::*; pub use self::no_std::*;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
@ -10,3 +12,23 @@ mod std;
// re-export fontdb // re-export fontdb
pub use fontdb; pub use fontdb;
/// A value borrowed together with an [`FontSystem`]
pub struct BorrowedWithFontSystem<'a, T> {
pub(crate) inner: &'a mut T,
pub(crate) font_system: &'a mut FontSystem,
}
impl<'a, T> Deref for BorrowedWithFontSystem<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner
}
}
impl<'a, T> DerefMut for BorrowedWithFontSystem<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner
}
}

View file

@ -6,7 +6,7 @@ use alloc::{
vec::Vec, vec::Vec,
}; };
use crate::{Attrs, Font, FontMatches}; use crate::{Attrs, Font};
/// Access system fonts /// Access system fonts
pub struct FontSystem { pub struct FontSystem {
@ -43,35 +43,33 @@ impl FontSystem {
&self.db &self.db
} }
// Clippy false positive pub fn db_mut(&mut self) -> &mut fontdb::Database {
#[allow(clippy::needless_lifetimes)] &mut self.db
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
let face = self.db.face(id)?;
match Font::new(face) {
Some(font) => Some(Arc::new(font)),
None => {
log::warn!("failed to load font '{}'", face.post_script_name);
None
}
}
} }
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> { pub fn get_font(&self, id: fontdb::ID) -> Option<Arc<Font>> {
let mut fonts = Vec::new(); get_font(&self.db, id)
for face in self.db.faces() { }
if !attrs.matches(face) {
continue;
}
if let Some(font) = self.get_font(face.id) { pub fn get_font_matches(&mut self, attrs: Attrs) -> Arc<Vec<fontdb::ID>> {
fonts.push(font); let ids = self
} .db
} .faces()
.filter(|face| attrs.matches(face))
.map(|face| face.id)
.collect::<Vec<_>>();
Arc::new(FontMatches { Arc::new(ids)
locale: &self.locale, }
default_family: self.db.family_name(&attrs.family).to_string(), }
fonts,
}) fn get_font(db: &fontdb::Database, id: fontdb::ID) -> Option<Arc<Font>> {
let face = db.face(id)?;
match Font::new(face) {
Some(font) => Some(Arc::new(font)),
None => {
log::warn!("failed to load font '{}'", face.post_script_name);
None
}
} }
} }

View file

@ -1,26 +1,16 @@
// SPDX-License-Identifier: MIT OR Apache-2.0 // SPDX-License-Identifier: MIT OR Apache-2.0
use std::{ use std::{collections::HashMap, sync::Arc};
collections::HashMap,
sync::{Arc, Mutex},
};
use crate::{Attrs, AttrsOwned, Font, FontMatches}; use crate::{Attrs, AttrsOwned, Font};
#[ouroboros::self_referencing]
struct FontSystemInner {
locale: String,
db: fontdb::Database,
#[borrows(db)]
#[not_covariant]
font_cache: Mutex<HashMap<fontdb::ID, Option<Arc<Font<'this>>>>>,
#[borrows(locale, db)]
#[not_covariant]
font_matches_cache: Mutex<HashMap<AttrsOwned, Arc<FontMatches<'this>>>>,
}
/// Access system fonts /// Access system fonts
pub struct FontSystem(FontSystemInner); pub struct FontSystem {
locale: String,
db: fontdb::Database,
font_cache: HashMap<fontdb::ID, Option<Arc<Font>>>,
font_matches_cache: HashMap<AttrsOwned, Arc<Vec<fontdb::ID>>>,
}
impl FontSystem { impl FontSystem {
/// Create a new [`FontSystem`], that allows access to any installed system fonts /// Create a new [`FontSystem`], that allows access to any installed system fonts
@ -72,110 +62,75 @@ impl FontSystem {
} }
/// Create a new [`FontSystem`], manually specifying the current locale and font database. /// Create a new [`FontSystem`], manually specifying the current locale and font database.
pub fn new_with_locale_and_db(locale: String, mut db: fontdb::Database) -> Self { pub fn new_with_locale_and_db(locale: String, db: fontdb::Database) -> Self {
{ Self {
#[cfg(not(target_arch = "wasm32"))] locale,
let now = std::time::Instant::now(); db,
font_cache: HashMap::new(),
//TODO only do this on demand! font_matches_cache: HashMap::new(),
for id in db.faces().map(|face| face.id).collect::<Vec<_>>() {
unsafe {
db.make_shared_face_data(id);
}
}
#[cfg(not(target_arch = "wasm32"))]
log::info!(
"Mapped {} font faces in {}ms.",
db.len(),
now.elapsed().as_millis()
);
} }
Self(
FontSystemInnerBuilder {
locale,
db,
font_cache_builder: |_| Mutex::new(HashMap::new()),
font_matches_cache_builder: |_, _| Mutex::new(HashMap::new()),
}
.build(),
)
} }
pub fn locale(&self) -> &str { pub fn locale(&self) -> &str {
self.0.borrow_locale() &self.locale
} }
pub fn db(&self) -> &fontdb::Database { pub fn db(&self) -> &fontdb::Database {
self.0.borrow_db() &self.db
}
pub fn db_mut(&mut self) -> &mut fontdb::Database {
self.font_matches_cache.clear();
&mut self.db
} }
pub fn into_locale_and_db(self) -> (String, fontdb::Database) { pub fn into_locale_and_db(self) -> (String, fontdb::Database) {
let heads = self.0.into_heads(); (self.locale, self.db)
(heads.locale, heads.db)
} }
// Clippy false positive pub fn get_font(&mut self, id: fontdb::ID) -> Option<Arc<Font>> {
#[allow(clippy::needless_lifetimes)] get_font(&mut self.font_cache, &mut self.db, id)
pub fn get_font<'a>(&'a self, id: fontdb::ID) -> Option<Arc<Font<'a>>> {
self.0.with(|fields| get_font(&fields, id))
} }
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> { pub fn get_font_matches(&mut self, attrs: Attrs) -> Arc<Vec<fontdb::ID>> {
self.0.with(|fields| { self.font_matches_cache
let mut font_matches_cache = fields
.font_matches_cache
.lock()
.expect("failed to lock font matches cache");
//TODO: do not create AttrsOwned unless entry does not already exist //TODO: do not create AttrsOwned unless entry does not already exist
font_matches_cache .entry(AttrsOwned::new(attrs))
.entry(AttrsOwned::new(attrs)) .or_insert_with(|| {
.or_insert_with(|| { #[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm32"))] let now = std::time::Instant::now();
let now = std::time::Instant::now();
let mut fonts = Vec::new(); let ids = self
for face in fields.db.faces() { .db
if !attrs.matches(face) { .faces()
continue; .filter(|face| attrs.matches(face))
} .map(|face| face.id)
.collect::<Vec<_>>();
if let Some(font) = get_font(&fields, face.id) { #[cfg(not(target_arch = "wasm32"))]
fonts.push(font); {
} let elapsed = now.elapsed();
} log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
}
let font_matches = Arc::new(FontMatches { Arc::new(ids)
locale: fields.locale, })
default_family: fields.db.family_name(&attrs.family).to_string(), .clone()
fonts,
});
#[cfg(not(target_arch = "wasm32"))]
{
let elapsed = now.elapsed();
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
}
font_matches
})
.clone()
})
} }
} }
fn get_font<'b>( fn get_font(
fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>, font_cache: &mut HashMap<fontdb::ID, Option<Arc<Font>>>,
db: &mut fontdb::Database,
id: fontdb::ID, id: fontdb::ID,
) -> Option<Arc<Font<'b>>> { ) -> Option<Arc<Font>> {
fields font_cache
.font_cache
.lock()
.expect("failed to lock font cache")
.entry(id) .entry(id)
.or_insert_with(|| { .or_insert_with(|| {
let face = fields.db.face(id)?; unsafe {
db.make_shared_face_data(id);
}
let face = db.face(id)?;
match Font::new(face) { match Font::new(face) {
Some(font) => Some(Arc::new(font)), Some(font) => Some(Arc::new(font)),
None => { None => {

View file

@ -15,7 +15,7 @@
//! use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics}; //! use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics};
//! //!
//! // A FontSystem provides access to detected system fonts, create one per application //! // A FontSystem provides access to detected system fonts, create one per application
//! let font_system = FontSystem::new(); //! let mut font_system = FontSystem::new();
//! //!
//! // A SwashCache stores rasterized glyphs, create one per application //! // A SwashCache stores rasterized glyphs, create one per application
//! let mut swash_cache = SwashCache::new(); //! let mut swash_cache = SwashCache::new();
@ -24,7 +24,10 @@
//! let metrics = Metrics::new(14.0, 20.0); //! let metrics = Metrics::new(14.0, 20.0);
//! //!
//! // A Buffer provides shaping and layout for a UTF-8 string, create one per text widget //! // A Buffer provides shaping and layout for a UTF-8 string, create one per text widget
//! let mut buffer = Buffer::new(&font_system, metrics); //! let mut buffer = Buffer::new(&mut font_system, metrics);
//!
//! // Borrow buffer together with the font system for more convenient method calls
//! let mut buffer = buffer.borrow_with(&mut font_system);
//! //!
//! // Set a size for the text buffer, in pixels //! // Set a size for the text buffer, in pixels
//! buffer.set_size(80.0, 25.0); //! buffer.set_size(80.0, 25.0);

View file

@ -21,7 +21,7 @@ fn shape_fallback(
) -> (Vec<ShapeGlyph>, Vec<usize>) { ) -> (Vec<ShapeGlyph>, Vec<usize>) {
let run = &line[start_run..end_run]; 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;
let mut buffer = rustybuzz::UnicodeBuffer::new(); let mut buffer = rustybuzz::UnicodeBuffer::new();
buffer.set_direction(if span_rtl { buffer.set_direction(if span_rtl {
@ -35,7 +35,7 @@ fn shape_fallback(
let rtl = matches!(buffer.direction(), rustybuzz::Direction::RightToLeft); let rtl = matches!(buffer.direction(), rustybuzz::Direction::RightToLeft);
assert_eq!(rtl, span_rtl); assert_eq!(rtl, span_rtl);
let glyph_buffer = rustybuzz::shape(&font.rustybuzz, &[], buffer); let glyph_buffer = rustybuzz::shape(font.rustybuzz(), &[], buffer);
let glyph_infos = glyph_buffer.glyph_infos(); let glyph_infos = glyph_buffer.glyph_infos();
let glyph_positions = glyph_buffer.glyph_positions(); let glyph_positions = glyph_buffer.glyph_positions();
@ -61,7 +61,7 @@ fn shape_fallback(
y_advance, y_advance,
x_offset, x_offset,
y_offset, y_offset,
font_id: font.info.id, font_id: font.id(),
glyph_id: info.glyph_id.try_into().expect("failed to cast glyph ID"), glyph_id: info.glyph_id.try_into().expect("failed to cast glyph ID"),
//TODO: color should not be related to shaping //TODO: color should not be related to shaping
color_opt: attrs.color_opt, color_opt: attrs.color_opt,
@ -98,7 +98,7 @@ fn shape_fallback(
} }
fn shape_run( fn shape_run(
font_system: &FontSystem, font_system: &mut FontSystem,
line: &str, line: &str,
attrs_list: &AttrsList, attrs_list: &AttrsList,
start_run: usize, start_run: usize,
@ -122,24 +122,15 @@ fn shape_run(
let attrs = attrs_list.get_span(start_run); let attrs = attrs_list.get_span(start_run);
let font_matches = font_system.get_font_matches(attrs); let fonts = font_system.get_font_matches(attrs);
let default_families = [font_matches.default_family.as_str()]; let default_families = [&attrs.family];
let mut font_iter = FontFallbackIter::new( let mut font_iter = FontFallbackIter::new(font_system, &fonts, &default_families, scripts);
&font_matches.fonts,
&default_families,
scripts,
font_matches.locale,
);
let (mut glyphs, mut missing) = shape_fallback( let font = font_iter.next().expect("no default font found");
font_iter.next().expect("no default font found"),
line, let (mut glyphs, mut missing) =
attrs_list, shape_fallback(&font, line, attrs_list, start_run, end_run, span_rtl);
start_run,
end_run,
span_rtl,
);
//TODO: improve performance! //TODO: improve performance!
while !missing.is_empty() { while !missing.is_empty() {
@ -148,9 +139,12 @@ fn shape_run(
None => break, None => break,
}; };
log::trace!("Evaluating fallback with font '{}'", font.name()); log::trace!(
"Evaluating fallback with font '{}'",
font_iter.face_name(font.id())
);
let (mut fb_glyphs, fb_missing) = let (mut fb_glyphs, fb_missing) =
shape_fallback(font, line, attrs_list, start_run, end_run, span_rtl); shape_fallback(&font, line, attrs_list, start_run, end_run, span_rtl);
// Insert all matching glyphs // Insert all matching glyphs
let mut fb_i = 0; let mut fb_i = 0;
@ -278,7 +272,7 @@ pub struct ShapeWord {
impl ShapeWord { impl ShapeWord {
pub fn new( pub fn new(
font_system: &FontSystem, font_system: &mut FontSystem,
line: &str, line: &str,
attrs_list: &AttrsList, attrs_list: &AttrsList,
word_range: Range<usize>, word_range: Range<usize>,
@ -352,7 +346,7 @@ pub struct ShapeSpan {
impl ShapeSpan { impl ShapeSpan {
pub fn new( pub fn new(
font_system: &FontSystem, font_system: &mut FontSystem,
line: &str, line: &str,
attrs_list: &AttrsList, attrs_list: &AttrsList,
span_range: Range<usize>, span_range: Range<usize>,
@ -443,7 +437,7 @@ impl ShapeLine {
/// # Panics /// # Panics
/// ///
/// Will panic if `line` contains more than one paragraph. /// Will panic if `line` contains more than one paragraph.
pub fn new(font_system: &FontSystem, line: &str, attrs_list: &AttrsList) -> Self { pub fn new(font_system: &mut FontSystem, line: &str, attrs_list: &AttrsList) -> Self {
let mut spans = Vec::new(); let mut spans = Vec::new();
let bidi = unicode_bidi::BidiInfo::new(line, None); let bidi = unicode_bidi::BidiInfo::new(line, None);

View file

@ -16,7 +16,7 @@ pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
pub use swash::zeno::{Command, Placement}; pub use swash::zeno::{Command, Placement};
fn swash_image( fn swash_image(
font_system: &FontSystem, font_system: &mut FontSystem,
context: &mut ScaleContext, context: &mut ScaleContext,
cache_key: CacheKey, cache_key: CacheKey,
) -> Option<SwashImage> { ) -> Option<SwashImage> {
@ -57,7 +57,7 @@ fn swash_image(
} }
fn swash_outline_commands( fn swash_outline_commands(
font_system: &FontSystem, font_system: &mut FontSystem,
context: &mut ScaleContext, context: &mut ScaleContext,
cache_key: CacheKey, cache_key: CacheKey,
) -> Option<Vec<swash::zeno::Command>> { ) -> Option<Vec<swash::zeno::Command>> {
@ -109,7 +109,7 @@ impl SwashCache {
/// Create a swash Image from a cache key, without caching results /// Create a swash Image from a cache key, without caching results
pub fn get_image_uncached( pub fn get_image_uncached(
&mut self, &mut self,
font_system: &FontSystem, font_system: &mut FontSystem,
cache_key: CacheKey, cache_key: CacheKey,
) -> Option<SwashImage> { ) -> Option<SwashImage> {
swash_image(font_system, &mut self.context, cache_key) swash_image(font_system, &mut self.context, cache_key)
@ -118,7 +118,7 @@ impl SwashCache {
/// Create a swash Image from a cache key, caching results /// Create a swash Image from a cache key, caching results
pub fn get_image( pub fn get_image(
&mut self, &mut self,
font_system: &FontSystem, font_system: &mut FontSystem,
cache_key: CacheKey, cache_key: CacheKey,
) -> &Option<SwashImage> { ) -> &Option<SwashImage> {
self.image_cache self.image_cache
@ -128,7 +128,7 @@ impl SwashCache {
pub fn get_outline_commands( pub fn get_outline_commands(
&mut self, &mut self,
font_system: &FontSystem, font_system: &mut FontSystem,
cache_key: CacheKey, cache_key: CacheKey,
) -> Option<&[swash::zeno::Command]> { ) -> Option<&[swash::zeno::Command]> {
self.outline_command_cache self.outline_command_cache
@ -140,7 +140,7 @@ impl SwashCache {
/// Enumerate pixels in an Image, use `with_image` for better performance /// Enumerate pixels in an Image, use `with_image` for better performance
pub fn with_pixels<F: FnMut(i32, i32, Color)>( pub fn with_pixels<F: FnMut(i32, i32, Color)>(
&mut self, &mut self,
font_system: &FontSystem, font_system: &mut FontSystem,
cache_key: CacheKey, cache_key: CacheKey,
base: Color, base: Color,
mut f: F, mut f: F,