Make swash optional, add swash cache abstraction
This commit is contained in:
parent
efc5108a2d
commit
1c341f3126
7 changed files with 148 additions and 109 deletions
|
|
@ -9,13 +9,16 @@ license = "MIT OR Apache-2.0"
|
||||||
fontdb = "0.9"
|
fontdb = "0.9"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
rustybuzz = "0.5"
|
rustybuzz = "0.5"
|
||||||
swash = "0.1"
|
swash = { version = "0.1", optional = true }
|
||||||
sys-locale = "0.2"
|
sys-locale = "0.2"
|
||||||
unicode-bidi = "0.3"
|
unicode-bidi = "0.3"
|
||||||
unicode-linebreak = "0.1"
|
unicode-linebreak = "0.1"
|
||||||
unicode-script = "0.5"
|
unicode-script = "0.5"
|
||||||
unicode-segmentation = "1.7"
|
unicode-segmentation = "1.7"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["swash"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"examples/*",
|
"examples/*",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
cmp::{self, Ordering},
|
cmp,
|
||||||
fmt,
|
fmt,
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
|
|
@ -176,6 +176,8 @@ pub struct TextBuffer<'a> {
|
||||||
cursor: TextCursor,
|
cursor: TextCursor,
|
||||||
select_opt: Option<TextCursor>,
|
select_opt: Option<TextCursor>,
|
||||||
pub redraw: bool,
|
pub redraw: bool,
|
||||||
|
#[cfg(feature = "swash")]
|
||||||
|
cache: crate::SwashCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TextBuffer<'a> {
|
impl<'a> TextBuffer<'a> {
|
||||||
|
|
@ -193,6 +195,8 @@ impl<'a> TextBuffer<'a> {
|
||||||
cursor: TextCursor::default(),
|
cursor: TextCursor::default(),
|
||||||
select_opt: None,
|
select_opt: None,
|
||||||
redraw: false,
|
redraw: false,
|
||||||
|
#[cfg(feature = "swash")]
|
||||||
|
cache: crate::SwashCache::new(),
|
||||||
};
|
};
|
||||||
buffer.set_text("");
|
buffer.set_text("");
|
||||||
buffer
|
buffer
|
||||||
|
|
@ -793,7 +797,8 @@ impl<'a> TextBuffer<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the buffer
|
/// Draw the buffer
|
||||||
pub fn draw<F>(&self, color: u32, mut f: F)
|
#[cfg(feature = "swash")]
|
||||||
|
pub fn draw<F>(&mut self, color: u32, mut f: F)
|
||||||
where F: FnMut(i32, i32, u32, u32, u32)
|
where F: FnMut(i32, i32, u32, u32, u32)
|
||||||
{
|
{
|
||||||
let font_size = self.metrics.font_size;
|
let font_size = self.metrics.font_size;
|
||||||
|
|
@ -869,9 +874,9 @@ impl<'a> TextBuffer<'a> {
|
||||||
// Highlight selection (TODO: HIGHLIGHT COLOR!)
|
// Highlight selection (TODO: HIGHLIGHT COLOR!)
|
||||||
if let Some(select) = self.select_opt {
|
if let Some(select) = self.select_opt {
|
||||||
let (start, end) = match select.line.cmp(&self.cursor.line) {
|
let (start, end) = match select.line.cmp(&self.cursor.line) {
|
||||||
Ordering::Greater => (self.cursor, select),
|
cmp::Ordering::Greater => (self.cursor, select),
|
||||||
Ordering::Less => (select, self.cursor),
|
cmp::Ordering::Less => (select, self.cursor),
|
||||||
Ordering::Equal => {
|
cmp::Ordering::Equal => {
|
||||||
/* select.line == self.cursor.line */
|
/* select.line == self.cursor.line */
|
||||||
if select.index < self.cursor.index {
|
if select.index < self.cursor.index {
|
||||||
(select, self.cursor)
|
(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;
|
line_y += line_height;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,6 @@ impl CacheKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type CacheItem = Option<swash::scale::image::Image>;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
pub enum SubpixelBin {
|
pub enum SubpixelBin {
|
||||||
Zero,
|
Zero,
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,12 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use std::{collections::HashMap, sync::Mutex};
|
|
||||||
|
|
||||||
use super::{CacheKey, CacheItem};
|
|
||||||
|
|
||||||
pub struct Font<'a> {
|
pub struct Font<'a> {
|
||||||
pub info: &'a fontdb::FaceInfo,
|
pub info: &'a fontdb::FaceInfo,
|
||||||
pub data: &'a [u8],
|
pub data: &'a [u8],
|
||||||
pub index: u32,
|
pub index: u32,
|
||||||
pub rustybuzz: rustybuzz::Face<'a>,
|
pub rustybuzz: rustybuzz::Face<'a>,
|
||||||
|
#[cfg(feature = "swash")]
|
||||||
pub swash: swash::FontRef<'a>,
|
pub swash: swash::FontRef<'a>,
|
||||||
pub scale_context: Mutex<swash::scale::ScaleContext>,
|
|
||||||
pub cache: Mutex<HashMap<CacheKey, CacheItem>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Font<'a> {
|
impl<'a> Font<'a> {
|
||||||
|
|
@ -21,9 +16,8 @@ impl<'a> Font<'a> {
|
||||||
data,
|
data,
|
||||||
index,
|
index,
|
||||||
rustybuzz: rustybuzz::Face::from_slice(data, index)?,
|
rustybuzz: rustybuzz::Face::from_slice(data, index)?,
|
||||||
|
#[cfg(feature = "swash")]
|
||||||
swash: swash::FontRef::from_index(data, index as usize)?,
|
swash: swash::FontRef::from_index(data, index as usize)?,
|
||||||
scale_context: Mutex::new(swash::scale::ScaleContext::new()),
|
|
||||||
cache: Mutex::new(HashMap::new()),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use super::{CacheKey, FontMatches};
|
use super::CacheKey;
|
||||||
|
|
||||||
pub struct FontLayoutGlyph {
|
pub struct FontLayoutGlyph {
|
||||||
pub start: usize,
|
pub start: usize,
|
||||||
|
|
@ -17,92 +17,3 @@ pub struct FontLayoutLine {
|
||||||
pub rtl: bool,
|
pub rtl: bool,
|
||||||
pub glyphs: Vec<FontLayoutGlyph>,
|
pub glyphs: Vec<FontLayoutGlyph>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FontLayoutLine {
|
|
||||||
pub fn draw<F: FnMut(i32, i32, u32)>(&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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,8 @@ mod buffer;
|
||||||
|
|
||||||
pub use self::font::*;
|
pub use self::font::*;
|
||||||
mod font;
|
mod font;
|
||||||
|
|
||||||
|
#[cfg(feature = "swash")]
|
||||||
|
pub use self::swash::*;
|
||||||
|
#[cfg(feature = "swash")]
|
||||||
|
mod swash;
|
||||||
|
|
|
||||||
118
src/swash.rs
Normal file
118
src/swash.rs
Normal file
|
|
@ -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<CacheKey, Option<SwashImage>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<F: FnMut(&Option<SwashImage>)>(
|
||||||
|
&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<F: FnMut(i32, i32, u32)>(
|
||||||
|
&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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue