diff --git a/Cargo.toml b/Cargo.toml index 5e70b64..863667d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,13 +9,16 @@ license = "MIT OR Apache-2.0" fontdb = "0.9" log = "0.4" rustybuzz = "0.5" -swash = "0.1" +swash = { version = "0.1", optional = true } sys-locale = "0.2" unicode-bidi = "0.3" unicode-linebreak = "0.1" unicode-script = "0.5" unicode-segmentation = "1.7" +[features] +default = ["swash"] + [workspace] members = [ "examples/*", diff --git a/src/buffer.rs b/src/buffer.rs index 31701d1..261aadc 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use std::{ - cmp::{self, Ordering}, + cmp, fmt, time::Instant, }; @@ -176,6 +176,8 @@ pub struct TextBuffer<'a> { cursor: TextCursor, select_opt: Option, pub redraw: bool, + #[cfg(feature = "swash")] + cache: crate::SwashCache, } impl<'a> TextBuffer<'a> { @@ -193,6 +195,8 @@ impl<'a> TextBuffer<'a> { cursor: TextCursor::default(), select_opt: None, redraw: false, + #[cfg(feature = "swash")] + cache: crate::SwashCache::new(), }; buffer.set_text(""); buffer @@ -793,7 +797,8 @@ impl<'a> TextBuffer<'a> { } /// Draw the buffer - pub fn draw(&self, color: u32, mut f: F) + #[cfg(feature = "swash")] + pub fn draw(&mut self, color: u32, mut f: F) where F: FnMut(i32, i32, u32, u32, u32) { let font_size = self.metrics.font_size; @@ -869,9 +874,9 @@ impl<'a> TextBuffer<'a> { // Highlight selection (TODO: HIGHLIGHT COLOR!) if let Some(select) = self.select_opt { let (start, end) = match select.line.cmp(&self.cursor.line) { - Ordering::Greater => (self.cursor, select), - Ordering::Less => (select, self.cursor), - Ordering::Equal => { + cmp::Ordering::Greater => (self.cursor, select), + cmp::Ordering::Less => (select, self.cursor), + cmp::Ordering::Equal => { /* select.line == self.cursor.line */ if select.index < self.cursor.index { (select, self.cursor) @@ -975,9 +980,14 @@ impl<'a> TextBuffer<'a> { ); } - layout_line.draw(self.font_matches, color, |x, y, color| { - f(x, line_y + y, 1, 1, color); - }); + + + for glyph in layout_line.glyphs.iter() { + let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int); + self.cache.with_pixels(self.font_matches, cache_key, color, |x, y, color| { + f(x_int + x, line_y + y_int + y, 1, 1, color) + }); + } line_y += line_height; } diff --git a/src/font/cache.rs b/src/font/cache.rs index b414477..bd4cd34 100644 --- a/src/font/cache.rs +++ b/src/font/cache.rs @@ -32,8 +32,6 @@ impl CacheKey { } } -pub type CacheItem = Option; - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum SubpixelBin { Zero, diff --git a/src/font/font.rs b/src/font/font.rs index ccadf68..525d42a 100644 --- a/src/font/font.rs +++ b/src/font/font.rs @@ -1,17 +1,12 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 -use std::{collections::HashMap, sync::Mutex}; - -use super::{CacheKey, CacheItem}; - pub struct Font<'a> { pub info: &'a fontdb::FaceInfo, pub data: &'a [u8], pub index: u32, pub rustybuzz: rustybuzz::Face<'a>, + #[cfg(feature = "swash")] pub swash: swash::FontRef<'a>, - pub scale_context: Mutex, - pub cache: Mutex>, } impl<'a> Font<'a> { @@ -21,9 +16,8 @@ impl<'a> Font<'a> { data, index, rustybuzz: rustybuzz::Face::from_slice(data, index)?, + #[cfg(feature = "swash")] swash: swash::FontRef::from_index(data, index as usize)?, - scale_context: Mutex::new(swash::scale::ScaleContext::new()), - cache: Mutex::new(HashMap::new()), }) } } diff --git a/src/font/layout.rs b/src/font/layout.rs index 1a15ce6..09725ba 100644 --- a/src/font/layout.rs +++ b/src/font/layout.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 -use super::{CacheKey, FontMatches}; +use super::CacheKey; pub struct FontLayoutGlyph { pub start: usize, @@ -17,92 +17,3 @@ pub struct FontLayoutLine { pub rtl: bool, pub glyphs: Vec, } - -impl FontLayoutLine { - pub fn draw(&self, matches: &FontMatches<'_>, base: u32, mut f: F) { - for glyph in self.glyphs.iter() { - use swash::scale::{Render, Source, StrikeWith}; - use swash::zeno::{Format, Vector}; - - let font = match matches.get_font(&glyph.cache_key.font_id) { - Some(some) => some, - None => { - log::warn!("did not find font {:?}", glyph.cache_key.font_id); - continue; - }, - }; - - let mut cache = font.cache.lock().unwrap(); - - let (cache_key, x_int, y_int) = (glyph.cache_key, glyph.x_int, glyph.y_int); - - let image_opt = cache.entry(cache_key).or_insert_with(|| { - let mut scale_context = font.scale_context.lock().unwrap(); - - // Build the scaler - let mut scaler = scale_context - .builder(font.swash) - .size(cache_key.font_size as f32) - .hint(true) - .build(); - - // Compute the fractional offset-- you'll likely want to quantize this - // in a real renderer - let offset = - Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float()); - - // Select our source order - Render::new(&[ - // Color outline with the first palette - Source::ColorOutline(0), - // Color bitmap with best fit selection mode - Source::ColorBitmap(StrikeWith::BestFit), - // Standard scalable outline - Source::Outline, - ]) - // Select a subpixel format - .format(Format::Alpha) - // Apply the fractional offset - .offset(offset) - // Render the image - .render(&mut scaler, cache_key.glyph_id) - }); - - if let Some(ref image) = image_opt { - use swash::scale::image::Content; - - let x = x_int + image.placement.left; - let y = y_int - image.placement.top; - - match image.content { - Content::Mask => { - let mut i = 0; - for off_y in 0..image.placement.height as i32 { - for off_x in 0..image.placement.width as i32 { - let color = (image.data[i] as u32) << 24 | base & 0xFFFFFF; - f(x + off_x, y + off_y, color); - i += 1; - } - } - } - Content::Color => { - let mut i = 0; - for off_y in 0..image.placement.height as i32 { - for off_x in 0..image.placement.width as i32 { - let color = (image.data[i + 3] as u32) << 24 - | (image.data[i] as u32) << 16 - | (image.data[i + 1] as u32) << 8 - | (image.data[i + 2] as u32); - f(x + off_x, y + off_y, color); - i += 4; - } - } - } - Content::SubpixelMask => { - log::warn!("TODO: SubpixelMask"); - } - } - } - } - } -} diff --git a/src/lib.rs b/src/lib.rs index c7a69a3..9ab2f13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,8 @@ mod buffer; pub use self::font::*; mod font; + +#[cfg(feature = "swash")] +pub use self::swash::*; +#[cfg(feature = "swash")] +mod swash; diff --git a/src/swash.rs b/src/swash.rs new file mode 100644 index 0000000..577c071 --- /dev/null +++ b/src/swash.rs @@ -0,0 +1,118 @@ +use std::collections::HashMap; +use swash::scale::{ScaleContext, image::Content}; +use swash::scale::{Render, Source, StrikeWith}; +use swash::zeno::{Format, Vector}; + +use crate::{CacheKey, FontMatches}; + +pub use swash::scale::image::Image as SwashImage; + +pub struct SwashCache { + context: ScaleContext, + cache: HashMap>, +} + +impl SwashCache { + /// Create a new swash cache + pub fn new() -> Self { + Self { + context: ScaleContext::new(), + cache: HashMap::new() + } + } + + /// Create a swash Image from a cache key, caching results + pub fn with_image)>( + &mut self, + matches: &FontMatches<'_>, + cache_key: CacheKey, + mut f: F + ) { + let image_opt = self.cache.entry(cache_key).or_insert_with(|| { + let font = match matches.get_font(&cache_key.font_id) { + Some(some) => some, + None => { + log::warn!("did not find font {:?}", cache_key.font_id); + return None; + }, + }; + + // Build the scaler + let mut scaler = self.context + .builder(font.swash) + .size(cache_key.font_size as f32) + .hint(true) + .build(); + + // Compute the fractional offset-- you'll likely want to quantize this + // in a real renderer + let offset = + Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float()); + + // Select our source order + Render::new(&[ + // Color outline with the first palette + Source::ColorOutline(0), + // Color bitmap with best fit selection mode + Source::ColorBitmap(StrikeWith::BestFit), + // Standard scalable outline + Source::Outline, + ]) + // Select a subpixel format + .format(Format::Alpha) + // Apply the fractional offset + .offset(offset) + // Render the image + .render(&mut scaler, cache_key.glyph_id) + }); + + f(image_opt); + } + + /// Enumerate pixels in an Image, use `with_image` for better performance + pub fn with_pixels( + &mut self, + matches: &FontMatches<'_>, + cache_key: CacheKey, + base: u32, + mut f: F + ) { + self.with_image(matches, cache_key, |image_opt| { + if let Some(image) = image_opt { + let x = image.placement.left; + let y = -image.placement.top; + + match image.content { + Content::Mask => { + let mut i = 0; + for off_y in 0..image.placement.height as i32 { + for off_x in 0..image.placement.width as i32 { + //TODO: blend base alpha? + let color = (image.data[i] as u32) << 24 | base & 0xFFFFFF; + f(x + off_x, y + off_y, color); + i += 1; + } + } + } + Content::Color => { + let mut i = 0; + for off_y in 0..image.placement.height as i32 { + for off_x in 0..image.placement.width as i32 { + //TODO: blend base alpha? + let color = (image.data[i + 3] as u32) << 24 + | (image.data[i] as u32) << 16 + | (image.data[i + 1] as u32) << 8 + | (image.data[i + 2] as u32); + f(x + off_x, y + off_y, color); + i += 4; + } + } + } + Content::SubpixelMask => { + log::warn!("TODO: SubpixelMask"); + } + } + } + }); + } +}