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 }
libm = "0.2.6"
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"] }
swash = { version = "0.1.6", optional = true }
syntect = { version = "5.0.0", optional = true }
@ -35,6 +35,7 @@ no_std = [
std = [
"fontdb/memmap",
"fontdb/std",
"ouroboros/std",
"rustybuzz/std",
"sys-locale",
"unicode-bidi/std",

View file

@ -23,7 +23,7 @@ use self::text_box::text_box;
mod text_box;
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();
}
@ -112,6 +112,8 @@ pub enum Message {
impl Window {
pub fn open(&mut self, path: PathBuf) {
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) {
Ok(()) => {
log::info!("opened '{}'", path.display());
@ -137,7 +139,10 @@ impl Application for Window {
.family(cosmic_text::Family::Monospace);
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,
"base16-eighties.dark",
)
@ -169,11 +174,11 @@ impl Application for Window {
if let Some(path) = &self.path_opt {
format!(
"COSMIC Text - {} - {}",
FONT_SYSTEM.locale(),
FONT_SYSTEM.lock().unwrap().locale(),
path.display()
)
} 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) => {
self.font_size = font_size;
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) => {
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) => {
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| {
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;
if let Some(select) = editor.select_opt() {
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()));
//TODO: do we have to immediately shape?
line.shape(&crate::FONT_SYSTEM);
line.shape(&mut FONT_SYSTEM.lock().unwrap());
let text = Self {
line,
@ -186,7 +186,7 @@ where
};
cache.with_pixels(
&FONT_SYSTEM,
&mut FONT_SYSTEM.lock().unwrap(),
cache_key,
glyph_color,
|pixel_x, pixel_y, color| {

View file

@ -1,5 +1,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::FONT_SYSTEM;
use super::text;
use cosmic::{
iced_native::{
@ -66,11 +68,11 @@ pub fn text_box<Editor>(editor: &Mutex<Editor>) -> TextBox<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
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
Renderer::Theme: StyleSheet,
Editor: Edit<'editor>,
Editor: Edit,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
@ -93,7 +95,10 @@ where
//TODO: allow lazy shape
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;
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_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.shape_as_needed();
@ -232,6 +241,8 @@ where
) -> Status {
let state = tree.state.downcast_mut::<State>();
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;
match event {
@ -330,12 +341,11 @@ where
}
}
impl<'a, 'editor, Editor, Message, Renderer> From<TextBox<'a, Editor>>
for Element<'a, Message, Renderer>
impl<'a, Editor, Message, Renderer> From<TextBox<'a, Editor>> for Element<'a, Message, Renderer>
where
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
Renderer::Theme: StyleSheet,
Editor: Edit<'editor>,
Editor: Edit,
{
fn from(text_box: TextBox<'a, Editor>) -> Self {
Self::new(text_box)

View file

@ -40,7 +40,7 @@ fn main() {
)
.unwrap();
let font_system = FontSystem::new();
let mut font_system = FontSystem::new();
let syntax_system = SyntaxSystem::new();
@ -58,7 +58,7 @@ fn main() {
let line_x = 8.0 * display_scale;
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,
"base16-eighties.dark",
)
@ -67,6 +67,8 @@ fn main() {
#[cfg(feature = "vi")]
let mut editor = cosmic_text::ViEditor::new(editor);
let mut editor = editor.borrow_with(&mut font_system);
editor
.buffer_mut()
.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
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 std::{env, fs, process, time::Instant};
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 font_color = Color::rgb(0xFF, 0xFF, 0xFF);
@ -32,7 +38,7 @@ fn main() {
env_logger::init();
let display_scale = 1.0;
let font_system = FontSystem::new();
let mut font_system = FontSystem::new();
let mut window = Window::new_flags(
-1,
@ -54,11 +60,15 @@ fn main() {
];
let font_size_default = 1; // Body
let mut buffer = Buffer::new(&font_system, font_sizes[font_size_default]);
buffer.set_size(window.width() as f32, window.height() as f32);
let mut buffer = Buffer::new(&mut font_system, font_sizes[font_size_default]);
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.borrow_with(&mut font_system);
let mut swash_cache = SwashCache::new();
let text = if let Some(arg) = env::args().nth(1) {

View file

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

View file

@ -6,7 +6,7 @@ use termion::{color, cursor};
fn main() {
// 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
let mut swash_cache = SwashCache::new();
@ -15,7 +15,9 @@ fn main() {
let metrics = Metrics::new(14.0, 20.0);
// 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
let width = 80u16;

View file

@ -10,7 +10,10 @@ use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")]
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
#[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`]
pub struct LayoutRunIter<'a, 'b> {
buffer: &'b Buffer<'a>,
pub struct LayoutRunIter<'b> {
buffer: &'b Buffer,
line_i: usize,
layout_i: usize,
remaining_len: usize,
@ -178,8 +181,8 @@ pub struct LayoutRunIter<'a, 'b> {
total_layout: i32,
}
impl<'a, 'b> LayoutRunIter<'a, 'b> {
pub fn new(buffer: &'b Buffer<'a>) -> Self {
impl<'b> LayoutRunIter<'b> {
pub fn new(buffer: &'b Buffer) -> Self {
let total_layout_lines: usize = buffer
.lines
.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>;
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
#[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
pub struct Buffer<'a> {
font_system: &'a FontSystem,
pub struct Buffer {
/// [BufferLine]s (or paragraphs) of text in the buffer
pub lines: Vec<BufferLine>,
metrics: Metrics,
@ -309,17 +311,16 @@ pub struct Buffer<'a> {
wrap: Wrap,
}
impl<'a> Buffer<'a> {
impl Buffer {
/// Create a new [`Buffer`] with the provided [`FontSystem`] and [`Metrics`]
///
/// # Panics
///
/// 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");
let mut buffer = Self {
font_system,
lines: Vec::new(),
metrics,
width: 0.0,
@ -328,23 +329,29 @@ impl<'a> Buffer<'a> {
redraw: false,
wrap: Wrap::Word,
};
buffer.set_text("", Attrs::new());
buffer.set_text(font_system, "", Attrs::new());
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")))]
let instant = std::time::Instant::now();
for line in &mut self.lines {
if line.shape_opt().is_some() {
line.reset_layout();
line.layout(
self.font_system,
self.metrics.font_size,
self.width,
self.wrap,
);
line.layout(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
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")))]
let instant = std::time::Instant::now();
@ -369,12 +376,7 @@ impl<'a> Buffer<'a> {
if line.shape_opt().is_none() {
reshaped += 1;
}
let layout = line.layout(
self.font_system,
self.metrics.font_size,
self.width,
self.wrap,
);
let layout = line.layout(font_system, self.metrics.font_size, self.width, self.wrap);
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
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")))]
let instant = std::time::Instant::now();
@ -402,12 +404,7 @@ impl<'a> Buffer<'a> {
if line.shape_opt().is_none() {
reshaped += 1;
}
let layout = line.layout(
self.font_system,
self.metrics.font_size,
self.width,
self.wrap,
);
let layout = line.layout(font_system, self.metrics.font_size, self.width, self.wrap);
if line_i == cursor.line {
let layout_cursor = self.layout_cursor(&cursor);
layout_i += layout_cursor.layout as i32;
@ -430,15 +427,15 @@ impl<'a> Buffer<'a> {
self.scroll = layout_i - (lines - 1);
}
self.shape_until_scroll();
self.shape_until_scroll(font_system);
}
/// 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 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));
}
@ -473,26 +470,24 @@ impl<'a> Buffer<'a> {
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
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)?;
Some(line.shape(self.font_system))
Some(line.shape(font_system))
}
/// 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)?;
Some(line.layout(
self.font_system,
self.metrics.font_size,
self.width,
self.wrap,
))
Some(line.layout(font_system, self.metrics.font_size, self.width, self.wrap))
}
/// Get the current [`Metrics`]
@ -505,12 +500,12 @@ impl<'a> Buffer<'a> {
/// # Panics
///
/// 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 {
assert_ne!(metrics.font_size, 0.0, "font size cannot be 0");
self.metrics = metrics;
self.relayout();
self.shape_until_scroll();
self.relayout(font_system);
self.shape_until_scroll(font_system);
}
}
@ -520,11 +515,11 @@ impl<'a> Buffer<'a> {
}
/// 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 {
self.wrap = wrap;
self.relayout();
self.shape_until_scroll();
self.relayout(font_system);
self.shape_until_scroll(font_system);
}
}
@ -534,15 +529,15 @@ impl<'a> Buffer<'a> {
}
/// 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_height = height.max(0.0);
if clamped_width != self.width || clamped_height != self.height {
self.width = clamped_width;
self.height = clamped_height;
self.relayout();
self.shape_until_scroll();
self.relayout(font_system);
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
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();
for line in text.lines() {
self.lines
@ -579,7 +574,7 @@ impl<'a> Buffer<'a> {
self.scroll = 0;
self.shape_until_scroll();
self.shape_until_scroll(font_system);
}
/// True if a redraw is needed
@ -593,7 +588,7 @@ impl<'a> Buffer<'a> {
}
/// 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)
}
@ -700,8 +695,13 @@ impl<'a> Buffer<'a> {
/// Draw the buffer
#[cfg(feature = "swash")]
pub fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
where
pub fn draw<F>(
&self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
for run in self.layout_runs() {
@ -713,10 +713,70 @@ impl<'a> Buffer<'a> {
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);
});
}
}
}
}
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
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() {
self.shape_opt = Some(ShapeLine::new(font_system, &self.text, &self.attrs_list));
self.layout_opt = None;
@ -183,7 +183,7 @@ impl BufferLine {
/// Layout line, will cache results
pub fn layout(
&mut self,
font_system: &FontSystem,
font_system: &mut FontSystem,
font_size: f32,
width: f32,
wrap: Wrap,

View file

@ -10,20 +10,22 @@ use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")]
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
pub struct Editor<'a> {
buffer: Buffer<'a>,
pub struct Editor {
buffer: Buffer,
cursor: Cursor,
cursor_x_opt: Option<i32>,
select_opt: Option<Cursor>,
cursor_moved: bool,
}
impl<'a> Editor<'a> {
impl Editor {
/// Create a new [`Editor`] with the provided [`Buffer`]
pub fn new(buffer: Buffer<'a>) -> Self {
pub fn new(buffer: Buffer) -> Self {
Self {
buffer,
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
.buffer
.line_layout(cursor.line)
.line_layout(font_system, cursor.line)
.expect("layout not found");
let layout_line = match layout.get(cursor.layout) {
@ -68,12 +70,12 @@ impl<'a> Editor<'a> {
}
}
impl<'a> Edit<'a> for Editor<'a> {
fn buffer(&self) -> &Buffer<'a> {
impl Edit for Editor {
fn buffer(&self) -> &Buffer {
&self.buffer
}
fn buffer_mut(&mut self) -> &mut Buffer<'a> {
fn buffer_mut(&mut self) -> &mut 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 {
self.buffer.shape_until_cursor(self.cursor);
self.buffer.shape_until_cursor(font_system, self.cursor);
self.cursor_moved = false;
} 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;
}
fn action(&mut self, action: Action) {
fn action(&mut self, font_system: &mut FontSystem, action: Action) {
let old_cursor = self.cursor;
match action {
@ -333,9 +335,9 @@ impl<'a> Edit<'a> for Editor<'a> {
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(Action::Next);
self.action(font_system, Action::Next);
} 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);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(Action::Previous);
self.action(font_system, Action::Previous);
} 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
}
self.set_layout_cursor(cursor);
self.set_layout_cursor(font_system, cursor);
}
Action::Down => {
//TODO: make this preserve X as best as possible!
@ -381,7 +383,7 @@ impl<'a> Edit<'a> for Editor<'a> {
let layout_len = self
.buffer
.line_layout(cursor.line)
.line_layout(font_system, cursor.line)
.expect("layout not found")
.len();
@ -402,18 +404,18 @@ impl<'a> Edit<'a> for Editor<'a> {
cursor.glyph = cursor_x as usize; //TODO: glyph x position
}
self.set_layout_cursor(cursor);
self.set_layout_cursor(font_system, cursor);
}
Action::Home => {
let mut cursor = self.buffer.layout_cursor(&self.cursor);
cursor.glyph = 0;
self.set_layout_cursor(cursor);
self.set_layout_cursor(font_system, cursor);
self.cursor_x_opt = None;
}
Action::End => {
let mut cursor = self.buffer.layout_cursor(&self.cursor);
cursor.glyph = usize::max_value();
self.set_layout_cursor(cursor);
self.set_layout_cursor(font_system, cursor);
self.cursor_x_opt = None;
}
Action::ParagraphStart => {
@ -427,10 +429,10 @@ impl<'a> Edit<'a> for Editor<'a> {
self.buffer.set_redraw(true);
}
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 => {
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) => {
// TODO more efficient
@ -438,12 +440,12 @@ impl<'a> Edit<'a> for Editor<'a> {
match lines.cmp(&0) {
Ordering::Less => {
for _ in 0..-lines {
self.action(Action::Up);
self.action(font_system, Action::Up);
}
}
Ordering::Greater => {
for _ in 0..lines {
self.action(Action::Down);
self.action(font_system, Action::Down);
}
}
Ordering::Equal => {}
@ -459,7 +461,7 @@ impl<'a> Edit<'a> for Editor<'a> {
// Filter out special chars (except for tab), use Action instead
log::debug!("Refusing to insert control character {:?}", character);
} else if character == '\n' {
self.action(Action::Enter);
self.action(font_system, Action::Enter);
} else {
let mut str_buf = [0u8; 8];
let str_ref = character.encode_utf8(&mut str_buf);
@ -619,9 +621,9 @@ impl<'a> Edit<'a> for Editor<'a> {
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(Action::NextWord);
self.action(font_system, Action::NextWord);
} 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);
if let Some(rtl) = rtl_opt {
if rtl {
self.action(Action::PreviousWord);
self.action(font_system, Action::PreviousWord);
} 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
#[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
where
fn draw<F>(
&self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
let font_size = self.buffer.metrics().font_size;
@ -833,14 +840,9 @@ impl<'a> Edit<'a> for Editor<'a> {
None => color,
};
cache.with_pixels(
self.buffer.font_system(),
cache_key,
glyph_color,
|x, y, color| {
f(x_int + x, line_y as i32 + y_int + y, 1, 1, color);
},
);
cache.with_pixels(font_system, 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")]
use crate::Color;
use crate::{AttrsList, Buffer, Cursor};
use crate::{AttrsList, BorrowedWithFontSystem, Buffer, Cursor, FontSystem};
pub use self::editor::*;
mod editor;
@ -78,12 +78,26 @@ pub enum Action {
}
/// 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`]
fn buffer(&self) -> &Buffer<'a>;
fn buffer(&self) -> &Buffer;
/// 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
fn cursor(&self) -> Cursor;
@ -95,7 +109,7 @@ pub trait Edit<'a> {
fn set_select_opt(&mut self, select_opt: Option<Cursor>);
/// 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
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>);
/// 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
#[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, f: F)
where
fn draw<F>(
&self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
f: F,
) where
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 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 syntax_set: SyntaxSet,
@ -27,7 +30,7 @@ impl SyntaxSystem {
/// A wrapper of [`Editor`] with syntax highlighting provided by [`SyntaxSystem`]
pub struct SyntaxEditor<'a> {
editor: Editor<'a>,
editor: Editor,
syntax_system: &'a SyntaxSystem,
syntax: &'a SyntaxReference,
theme: &'a Theme,
@ -41,11 +44,7 @@ impl<'a> SyntaxEditor<'a> {
/// A good default theme name is "base16-eighties.dark".
///
/// Returns None if theme not found
pub fn new(
buffer: Buffer<'a>,
syntax_system: &'a SyntaxSystem,
theme_name: &str,
) -> Option<Self> {
pub fn new(buffer: Buffer, syntax_system: &'a SyntaxSystem, theme_name: &str) -> Option<Self> {
let editor = Editor::new(buffer);
let syntax = syntax_system.syntax_set.find_syntax_plain_text();
let theme = syntax_system.theme_set.themes.get(theme_name)?;
@ -69,13 +68,14 @@ impl<'a> SyntaxEditor<'a> {
#[cfg(feature = "std")]
pub fn load_text<P: AsRef<Path>>(
&mut self,
font_system: &mut FontSystem,
path: P,
attrs: crate::Attrs<'a>,
attrs: crate::Attrs,
) -> io::Result<()> {
let path = path.as_ref();
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
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> {
fn buffer(&self) -> &Buffer<'a> {
impl<'a> Edit for SyntaxEditor<'a> {
fn buffer(&self) -> &Buffer {
self.editor.buffer()
}
fn buffer_mut(&mut self) -> &mut Buffer<'a> {
fn buffer_mut(&mut self) -> &mut Buffer {
self.editor.buffer_mut()
}
@ -136,7 +136,7 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
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")]
let now = std::time::Instant::now();
@ -201,7 +201,7 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
line.set_wrap(Wrap::Word);
//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());
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> {
@ -241,18 +241,36 @@ impl<'a> Edit<'a> for SyntaxEditor<'a> {
self.editor.insert_string(data, attrs_list);
}
fn action(&mut self, action: Action) {
self.editor.action(action);
fn action(&mut self, font_system: &mut FontSystem, action: Action) {
self.editor.action(font_system, action);
}
/// Draw the editor
#[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, _color: Color, mut f: F)
where
fn draw<F>(
&self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
_color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
let size = self.buffer().size();
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 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)]
enum Mode {
@ -30,10 +33,11 @@ impl<'a> ViEditor<'a> {
#[cfg(feature = "std")]
pub fn load_text<P: AsRef<std::path::Path>>(
&mut self,
font_system: &mut FontSystem,
path: P,
attrs: crate::Attrs<'a>,
attrs: crate::Attrs,
) -> std::io::Result<()> {
self.editor.load_text(path, attrs)
self.editor.load_text(font_system, path, attrs)
}
/// Get the default background color
@ -47,12 +51,12 @@ impl<'a> ViEditor<'a> {
}
}
impl<'a> Edit<'a> for ViEditor<'a> {
fn buffer(&self) -> &Buffer<'a> {
impl<'a> Edit for ViEditor<'a> {
fn buffer(&self) -> &Buffer {
self.editor.buffer()
}
fn buffer_mut(&mut self) -> &mut Buffer<'a> {
fn buffer_mut(&mut self) -> &mut Buffer {
self.editor.buffer_mut()
}
@ -68,8 +72,8 @@ impl<'a> Edit<'a> for ViEditor<'a> {
self.editor.set_select_opt(select_opt);
}
fn shape_as_needed(&mut self) {
self.editor.shape_as_needed()
fn shape_as_needed(&mut self, font_system: &mut FontSystem) {
self.editor.shape_as_needed(font_system);
}
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);
}
fn action(&mut self, action: Action) {
fn action(&mut self, font_system: &mut FontSystem, action: Action) {
let old_mode = self.mode;
match self.mode {
@ -92,18 +96,18 @@ impl<'a> Edit<'a> for ViEditor<'a> {
Action::Insert(c) => match c {
// Enter insert mode after cursor
'a' => {
self.editor.action(Action::Right);
self.editor.action(font_system, Action::Right);
self.mode = Mode::Insert;
}
// Enter insert mode at end of line
'A' => {
self.editor.action(Action::End);
self.editor.action(font_system, Action::End);
self.mode = Mode::Insert;
}
// Change mode
'c' => {
if self.editor.select_opt().is_some() {
self.editor.action(Action::Delete);
self.editor.action(font_system, Action::Delete);
self.mode = Mode::Insert;
} else {
//TODO: change to next cursor movement
@ -112,7 +116,7 @@ impl<'a> Edit<'a> for ViEditor<'a> {
// Delete mode
'd' => {
if self.editor.select_opt().is_some() {
self.editor.action(Action::Delete);
self.editor.action(font_system, Action::Delete);
} else {
//TODO: delete to next cursor movement
}
@ -124,33 +128,33 @@ impl<'a> Edit<'a> for ViEditor<'a> {
// Enter insert mode at start of line
'I' => {
//TODO: soft home, skip whitespace
self.editor.action(Action::Home);
self.editor.action(font_system, Action::Home);
self.mode = Mode::Insert;
}
// Create line after and enter insert mode
'o' => {
self.editor.action(Action::End);
self.editor.action(Action::Enter);
self.editor.action(font_system, Action::End);
self.editor.action(font_system, Action::Enter);
self.mode = Mode::Insert;
}
// Create line before and enter insert mode
'O' => {
self.editor.action(Action::Home);
self.editor.action(Action::Enter);
self.editor.shape_as_needed(); // TODO: do not require this?
self.editor.action(Action::Up);
self.editor.action(font_system, Action::Home);
self.editor.action(font_system, Action::Enter);
self.editor.shape_as_needed(font_system); // TODO: do not require this?
self.editor.action(font_system, Action::Up);
self.mode = Mode::Insert;
}
// Left
'h' => self.editor.action(Action::Left),
'h' => self.editor.action(font_system, Action::Left),
// Top of screen
//TODO: 'H' => self.editor.action(Action::ScreenHigh),
// Down
'j' => self.editor.action(Action::Down),
'j' => self.editor.action(font_system, Action::Down),
// Up
'k' => self.editor.action(Action::Up),
'k' => self.editor.action(font_system, Action::Up),
// Right
'l' => self.editor.action(Action::Right),
'l' => self.editor.action(font_system, Action::Right),
// Bottom of screen
//TODO: 'L' => self.editor.action(Action::ScreenLow),
// Middle of screen
@ -168,23 +172,23 @@ impl<'a> Edit<'a> for ViEditor<'a> {
if self.editor.select_opt().is_some() {
self.editor.set_select_opt(None);
} else {
self.editor.action(Action::Home);
self.editor.action(font_system, Action::Home);
self.editor.set_select_opt(Some(self.editor.cursor()));
//TODO: set cursor_x_opt to max
self.editor.action(Action::End);
self.editor.action(font_system, Action::End);
}
}
// Remove character at cursor
'x' => self.editor.action(Action::Delete),
'x' => self.editor.action(font_system, Action::Delete),
// Remove character before cursor
'X' => self.editor.action(Action::Backspace),
'X' => self.editor.action(font_system, Action::Backspace),
// Go to start of line
'0' => self.editor.action(Action::Home),
'0' => self.editor.action(font_system, Action::Home),
// Go to end of line
'$' => self.editor.action(Action::End),
'$' => self.editor.action(font_system, Action::End),
// Go to start of line after whitespace
//TODO: implement this
'^' => self.editor.action(Action::Home),
'^' => self.editor.action(font_system, Action::Home),
// Enter command mode
':' => {
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 {
Action::Escape => {
let cursor = self.cursor();
let layout_cursor = self.buffer().layout_cursor(&cursor);
if layout_cursor.glyph > 0 {
self.editor.action(Action::Left);
self.editor.action(font_system, Action::Left);
}
self.mode = Mode::Normal;
}
_ => self.editor.action(action),
_ => self.editor.action(font_system, action),
},
_ => {
//TODO: other modes
@ -224,8 +228,13 @@ impl<'a> Edit<'a> for ViEditor<'a> {
}
#[cfg(feature = "swash")]
fn draw<F>(&self, cache: &mut crate::SwashCache, color: Color, mut f: F)
where
fn draw<F>(
&self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
let font_size = self.buffer().metrics().font_size;
@ -421,15 +430,22 @@ impl<'a> Edit<'a> for ViEditor<'a> {
None => color,
};
cache.with_pixels(
self.buffer().font_system(),
cache_key,
glyph_color,
|x, y, color| {
f(x_int + x, line_y as i32 + y_int + y, 1, 1, color);
},
);
cache.with_pixels(font_system, 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;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use fontdb::Family;
use unicode_script::Script;
use crate::Font;
use crate::{Font, FontSystem};
use self::platform::*;
@ -26,11 +27,11 @@ mod platform;
mod platform;
pub struct FontFallbackIter<'a> {
fonts: &'a [Arc<Font<'a>>],
default_families: &'a [&'a str],
font_system: &'a mut FontSystem,
font_ids: &'a [fontdb::ID],
default_families: &'a [&'a Family<'a>],
default_i: usize,
scripts: Vec<Script>,
locale: &'a str,
script_i: (usize, usize),
common_i: usize,
other_i: usize,
@ -39,17 +40,17 @@ pub struct FontFallbackIter<'a> {
impl<'a> FontFallbackIter<'a> {
pub fn new(
fonts: &'a [Arc<Font<'a>>],
default_families: &'a [&'a str],
font_system: &'a mut FontSystem,
font_ids: &'a [fontdb::ID],
default_families: &'a [&'a Family<'a>],
scripts: Vec<Script>,
locale: &'a str,
) -> Self {
Self {
fonts,
font_system,
font_ids,
default_families,
default_i: 0,
scripts,
locale,
script_i: (0, 0),
common_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 {
log::debug!(
"Failed to find any fallback for {:?} locale '{}': '{}'",
self.scripts,
self.locale,
self.font_system.locale(),
word
);
} else if self.other_i > 0 {
let font = &self.fonts[self.other_i - 1];
log::debug!(
"Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'",
self.scripts,
self.locale,
font.name(),
self.font_system.locale(),
self.face_name(self.font_ids[self.other_i - 1]),
word
);
} else if !self.scripts.is_empty() && self.common_i > 0 {
@ -79,24 +79,48 @@ impl<'a> FontFallbackIter<'a> {
log::debug!(
"Failed to find script fallback for {:?} locale '{}', used '{}': '{}'",
self.scripts,
self.locale,
self.font_system.locale(),
family,
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> {
type Item = &'a Arc<Font<'a>>;
type Item = Arc<Font>;
fn next(&mut self) -> Option<Self::Item> {
while self.default_i < self.default_families.len() {
let default_family = self.default_families[self.default_i];
self.default_i += 1;
for font in self.fonts.iter() {
if font.contains_family(default_family) {
return Some(font);
for id in self.font_ids.iter() {
let default_family = self
.font_system
.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() {
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() {
let script_family = script_families[self.script_i.1];
self.script_i.1 += 1;
for font in self.fonts.iter() {
if font.contains_family(script_family) {
return Some(font);
for id in self.font_ids.iter() {
if self.face_contains_family(*id, script_family) {
if let Some(font) = self.font_system.get_font(*id) {
return Some(font);
}
}
}
log::debug!(
"failed to find family '{}' for script {:?} and locale '{}'",
script_family,
script,
self.locale
self.font_system.locale(),
);
}
@ -129,9 +155,11 @@ impl<'a> Iterator for FontFallbackIter<'a> {
while self.common_i < common_families.len() {
let common_family = common_families[self.common_i];
self.common_i += 1;
for font in self.fonts.iter() {
if font.contains_family(common_family) {
return Some(font);
for id in self.font_ids.iter() {
if self.face_contains_family(*id, common_family) {
if let Some(font) = self.font_system.get_font(*id) {
return Some(font);
}
}
}
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 not evaluate fonts more than once!
let forbidden_families = forbidden_fallback();
while self.other_i < self.fonts.len() {
let font = &self.fonts[self.other_i];
while self.other_i < self.font_ids.len() {
let id = self.font_ids[self.other_i];
self.other_i += 1;
if forbidden_families
.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
use core::ops::Deref;
pub(crate) mod fallback;
pub use self::matches::*;
mod matches;
use alloc::sync::Arc;
pub use self::system::*;
mod system;
/// A font
pub struct Font<'a> {
pub info: &'a fontdb::FaceInfo,
pub data: &'a [u8],
pub rustybuzz: rustybuzz::Face<'a>,
#[cfg(feature = "swash")]
pub swash: (u32, swash::CacheKey),
pub struct Font(FontInner);
#[ouroboros::self_referencing]
#[allow(dead_code)]
struct FontInner {
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> {
pub fn new(info: &'a fontdb::FaceInfo) -> Option<Self> {
#[cfg(feature = "swash")]
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 {
fontdb::Source::Binary(data) => data.deref().as_ref(),
fontdb::Source::Binary(data) => Arc::clone(data),
#[cfg(feature = "std")]
fontdb::Source::File(path) => {
log::warn!("Unsupported fontdb Source::File('{}')", path.display());
return None;
}
#[cfg(feature = "std")]
fontdb::Source::SharedFile(_path, data) => data.deref().as_ref(),
fontdb::Source::SharedFile(_path, data) => Arc::clone(data),
};
Some(Self {
info,
data,
rustybuzz: rustybuzz::Face::from_slice(data, info.index)?,
#[cfg(feature = "swash")]
swash: {
let swash = swash::FontRef::from_index(data, info.index as usize)?;
(swash.offset, swash.key)
},
})
Some(Self(
FontInnerTryBuilder {
id: info.id,
swash: {
#[cfg(feature = "swash")]
let swash = {
let swash =
swash::FontRef::from_index((*data).as_ref(), info.index as usize)?;
(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 {
if let Some((name, _)) = self.info.families.first() {
name
} else {
&self.info.post_script_name
}
pub fn id(&self) -> fontdb::ID {
*self.0.borrow_id()
}
pub fn contains_family(&self, family: &str) -> bool {
self.info.families.iter().any(|(name, _)| name == family)
pub fn data(&self) -> &[u8] {
(**self.0.borrow_data()).as_ref()
}
pub fn rustybuzz(&self) -> &rustybuzz::Face {
self.0.borrow_rustybuzz()
}
#[cfg(feature = "swash")]
pub fn as_swash(&self) -> swash::FontRef {
let swash = self.0.borrow_swash();
swash::FontRef {
data: self.data,
offset: self.swash.0,
key: self.swash.1,
data: self.data(),
offset: swash.0,
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"))]
pub use self::no_std::*;
#[cfg(not(feature = "std"))]
@ -10,3 +12,23 @@ mod std;
// re-export 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,
};
use crate::{Attrs, Font, FontMatches};
use crate::{Attrs, Font};
/// Access system fonts
pub struct FontSystem {
@ -43,35 +43,33 @@ impl FontSystem {
&self.db
}
// Clippy false positive
#[allow(clippy::needless_lifetimes)]
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 db_mut(&mut self) -> &mut fontdb::Database {
&mut self.db
}
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
let mut fonts = Vec::new();
for face in self.db.faces() {
if !attrs.matches(face) {
continue;
}
pub fn get_font(&self, id: fontdb::ID) -> Option<Arc<Font>> {
get_font(&self.db, id)
}
if let Some(font) = self.get_font(face.id) {
fonts.push(font);
}
}
pub fn get_font_matches(&mut self, attrs: Attrs) -> Arc<Vec<fontdb::ID>> {
let ids = self
.db
.faces()
.filter(|face| attrs.matches(face))
.map(|face| face.id)
.collect::<Vec<_>>();
Arc::new(FontMatches {
locale: &self.locale,
default_family: self.db.family_name(&attrs.family).to_string(),
fonts,
})
Arc::new(ids)
}
}
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
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use std::{collections::HashMap, sync::Arc};
use crate::{Attrs, AttrsOwned, Font, FontMatches};
#[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>>>>,
}
use crate::{Attrs, AttrsOwned, Font};
/// 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 {
/// 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.
pub fn new_with_locale_and_db(locale: String, mut db: fontdb::Database) -> Self {
{
#[cfg(not(target_arch = "wasm32"))]
let now = std::time::Instant::now();
//TODO only do this on demand!
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()
);
pub fn new_with_locale_and_db(locale: String, db: fontdb::Database) -> Self {
Self {
locale,
db,
font_cache: HashMap::new(),
font_matches_cache: HashMap::new(),
}
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 {
self.0.borrow_locale()
&self.locale
}
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) {
let heads = self.0.into_heads();
(heads.locale, heads.db)
(self.locale, self.db)
}
// Clippy false positive
#[allow(clippy::needless_lifetimes)]
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(&mut self, id: fontdb::ID) -> Option<Arc<Font>> {
get_font(&mut self.font_cache, &mut self.db, id)
}
pub fn get_font_matches<'a>(&'a self, attrs: Attrs) -> Arc<FontMatches<'a>> {
self.0.with(|fields| {
let mut font_matches_cache = fields
.font_matches_cache
.lock()
.expect("failed to lock font matches cache");
pub fn get_font_matches(&mut self, attrs: Attrs) -> Arc<Vec<fontdb::ID>> {
self.font_matches_cache
//TODO: do not create AttrsOwned unless entry does not already exist
font_matches_cache
.entry(AttrsOwned::new(attrs))
.or_insert_with(|| {
#[cfg(not(target_arch = "wasm32"))]
let now = std::time::Instant::now();
.entry(AttrsOwned::new(attrs))
.or_insert_with(|| {
#[cfg(not(target_arch = "wasm32"))]
let now = std::time::Instant::now();
let mut fonts = Vec::new();
for face in fields.db.faces() {
if !attrs.matches(face) {
continue;
}
let ids = self
.db
.faces()
.filter(|face| attrs.matches(face))
.map(|face| face.id)
.collect::<Vec<_>>();
if let Some(font) = get_font(&fields, face.id) {
fonts.push(font);
}
}
#[cfg(not(target_arch = "wasm32"))]
{
let elapsed = now.elapsed();
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
}
let font_matches = Arc::new(FontMatches {
locale: fields.locale,
default_family: fields.db.family_name(&attrs.family).to_string(),
fonts,
});
#[cfg(not(target_arch = "wasm32"))]
{
let elapsed = now.elapsed();
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
}
font_matches
})
.clone()
})
Arc::new(ids)
})
.clone()
}
}
fn get_font<'b>(
fields: &ouroboros_impl_font_system_inner::BorrowedFields<'_, 'b>,
fn get_font(
font_cache: &mut HashMap<fontdb::ID, Option<Arc<Font>>>,
db: &mut fontdb::Database,
id: fontdb::ID,
) -> Option<Arc<Font<'b>>> {
fields
.font_cache
.lock()
.expect("failed to lock font cache")
) -> Option<Arc<Font>> {
font_cache
.entry(id)
.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) {
Some(font) => Some(Arc::new(font)),
None => {

View file

@ -15,7 +15,7 @@
//! use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics};
//!
//! // 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
//! let mut swash_cache = SwashCache::new();
@ -24,7 +24,10 @@
//! let metrics = Metrics::new(14.0, 20.0);
//!
//! // 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
//! buffer.set_size(80.0, 25.0);

View file

@ -21,7 +21,7 @@ fn shape_fallback(
) -> (Vec<ShapeGlyph>, Vec<usize>) {
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();
buffer.set_direction(if span_rtl {
@ -35,7 +35,7 @@ fn shape_fallback(
let rtl = matches!(buffer.direction(), rustybuzz::Direction::RightToLeft);
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_positions = glyph_buffer.glyph_positions();
@ -61,7 +61,7 @@ fn shape_fallback(
y_advance,
x_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"),
//TODO: color should not be related to shaping
color_opt: attrs.color_opt,
@ -98,7 +98,7 @@ fn shape_fallback(
}
fn shape_run(
font_system: &FontSystem,
font_system: &mut FontSystem,
line: &str,
attrs_list: &AttrsList,
start_run: usize,
@ -122,24 +122,15 @@ fn shape_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 mut font_iter = FontFallbackIter::new(
&font_matches.fonts,
&default_families,
scripts,
font_matches.locale,
);
let default_families = [&attrs.family];
let mut font_iter = FontFallbackIter::new(font_system, &fonts, &default_families, scripts);
let (mut glyphs, mut missing) = shape_fallback(
font_iter.next().expect("no default font found"),
line,
attrs_list,
start_run,
end_run,
span_rtl,
);
let font = font_iter.next().expect("no default font found");
let (mut glyphs, mut missing) =
shape_fallback(&font, line, attrs_list, start_run, end_run, span_rtl);
//TODO: improve performance!
while !missing.is_empty() {
@ -148,9 +139,12 @@ fn shape_run(
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) =
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
let mut fb_i = 0;
@ -278,7 +272,7 @@ pub struct ShapeWord {
impl ShapeWord {
pub fn new(
font_system: &FontSystem,
font_system: &mut FontSystem,
line: &str,
attrs_list: &AttrsList,
word_range: Range<usize>,
@ -352,7 +346,7 @@ pub struct ShapeSpan {
impl ShapeSpan {
pub fn new(
font_system: &FontSystem,
font_system: &mut FontSystem,
line: &str,
attrs_list: &AttrsList,
span_range: Range<usize>,
@ -443,7 +437,7 @@ impl ShapeLine {
/// # Panics
///
/// 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 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};
fn swash_image(
font_system: &FontSystem,
font_system: &mut FontSystem,
context: &mut ScaleContext,
cache_key: CacheKey,
) -> Option<SwashImage> {
@ -57,7 +57,7 @@ fn swash_image(
}
fn swash_outline_commands(
font_system: &FontSystem,
font_system: &mut FontSystem,
context: &mut ScaleContext,
cache_key: CacheKey,
) -> Option<Vec<swash::zeno::Command>> {
@ -109,7 +109,7 @@ impl SwashCache {
/// Create a swash Image from a cache key, without caching results
pub fn get_image_uncached(
&mut self,
font_system: &FontSystem,
font_system: &mut FontSystem,
cache_key: CacheKey,
) -> Option<SwashImage> {
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
pub fn get_image(
&mut self,
font_system: &FontSystem,
font_system: &mut FontSystem,
cache_key: CacheKey,
) -> &Option<SwashImage> {
self.image_cache
@ -128,7 +128,7 @@ impl SwashCache {
pub fn get_outline_commands(
&mut self,
font_system: &FontSystem,
font_system: &mut FontSystem,
cache_key: CacheKey,
) -> Option<&[swash::zeno::Command]> {
self.outline_command_cache
@ -140,7 +140,7 @@ impl SwashCache {
/// Enumerate pixels in an Image, use `with_image` for better performance
pub fn with_pixels<F: FnMut(i32, i32, Color)>(
&mut self,
font_system: &FontSystem,
font_system: &mut FontSystem,
cache_key: CacheKey,
base: Color,
mut f: F,