use crate::core::alignment;
use crate::core::text::{LineHeight, Shaping};
use crate::core::{Color, Font, Pixels, Point, Rectangle};
use crate::graphics::text::cache::{self, Cache};
use crate::graphics::text::paragraph;
use crate::graphics::text::FontSystem;
use rustc_hash::{FxHashMap, FxHashSet};
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::hash_map;
use std::sync::Arc;
#[allow(missing_debug_implementations)]
pub struct Pipeline {
font_system: FontSystem,
glyph_cache: GlyphCache,
cache: RefCell<Cache>,
}
impl Pipeline {
pub fn new() -> Self {
Pipeline {
font_system: FontSystem::new(),
glyph_cache: GlyphCache::new(),
cache: RefCell::new(Cache::new()),
}
}
pub fn font_system(&self) -> &FontSystem {
&self.font_system
}
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
self.font_system.get_mut().db_mut().load_font_source(
cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())),
);
self.cache = RefCell::new(Cache::new());
}
pub fn draw_paragraph(
&mut self,
_paragraph: ¶graph::Weak,
_position: Point,
_color: Color,
_scale_factor: f32,
_pixels: &mut tiny_skia::PixmapMut<'_>,
_clip_mask: Option<&tiny_skia::Mask>,
) {
}
pub fn draw(
&mut self,
content: &str,
bounds: Rectangle,
color: Color,
size: Pixels,
line_height: LineHeight,
font: Font,
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
shaping: Shaping,
scale_factor: f32,
pixels: &mut tiny_skia::PixmapMut<'_>,
clip_mask: Option<&tiny_skia::Mask>,
) {
let line_height = f32::from(line_height.to_absolute(size));
let font_system = self.font_system.get_mut();
let key = cache::Key {
bounds: bounds.size(),
content,
font,
size: size.into(),
line_height,
shaping,
};
let (_, buffer) = self.cache.get_mut().allocate(font_system, key);
let max_width = bounds.width * scale_factor;
let total_height = bounds.height * scale_factor;
let bounds = bounds * scale_factor;
let x = match horizontal_alignment {
alignment::Horizontal::Left => bounds.x,
alignment::Horizontal::Center => bounds.x - max_width / 2.0,
alignment::Horizontal::Right => bounds.x - max_width,
};
let y = match vertical_alignment {
alignment::Vertical::Top => bounds.y,
alignment::Vertical::Center => bounds.y - total_height / 2.0,
alignment::Vertical::Bottom => bounds.y - total_height,
};
let mut swash = cosmic_text::SwashCache::new();
for run in buffer.layout_runs() {
for glyph in run.glyphs {
let physical_glyph = glyph.physical((x, y), scale_factor);
if let Some((buffer, placement)) = self.glyph_cache.allocate(
physical_glyph.cache_key,
color,
font_system,
&mut swash,
) {
let pixmap = tiny_skia::PixmapRef::from_bytes(
buffer,
placement.width,
placement.height,
)
.expect("Create glyph pixel map");
pixels.draw_pixmap(
physical_glyph.x + placement.left,
physical_glyph.y - placement.top
+ (run.line_y * scale_factor).round() as i32,
pixmap,
&tiny_skia::PixmapPaint::default(),
tiny_skia::Transform::identity(),
clip_mask,
);
}
}
}
}
pub fn trim_cache(&mut self) {
self.cache.get_mut().trim();
self.glyph_cache.trim();
}
}
#[derive(Debug, Clone, Default)]
struct GlyphCache {
entries: FxHashMap<
(cosmic_text::CacheKey, [u8; 3]),
(Vec<u32>, cosmic_text::Placement),
>,
recently_used: FxHashSet<(cosmic_text::CacheKey, [u8; 3])>,
trim_count: usize,
}
impl GlyphCache {
const TRIM_INTERVAL: usize = 300;
fn new() -> Self {
GlyphCache::default()
}
fn allocate(
&mut self,
cache_key: cosmic_text::CacheKey,
color: Color,
font_system: &mut cosmic_text::FontSystem,
swash: &mut cosmic_text::SwashCache,
) -> Option<(&[u8], cosmic_text::Placement)> {
let [r, g, b, _a] = color.into_rgba8();
let key = (cache_key, [r, g, b]);
if let hash_map::Entry::Vacant(entry) = self.entries.entry(key) {
// TODO: Outline support
let image = swash.get_image_uncached(font_system, cache_key)?;
let glyph_size = image.placement.width as usize
* image.placement.height as usize;
if glyph_size == 0 {
return None;
}
let mut buffer = vec![0u32; glyph_size];
match image.content {
cosmic_text::SwashContent::Mask => {
let mut i = 0;
// TODO: Blend alpha
for _y in 0..image.placement.height {
for _x in 0..image.placement.width {
buffer[i] = bytemuck::cast(
tiny_skia::ColorU8::from_rgba(
b,
g,
r,
image.data[i],
)
.premultiply(),
);
i += 1;
}
}
}
cosmic_text::SwashContent::Color => {
let mut i = 0;
for _y in 0..image.placement.height {
for _x in 0..image.placement.width {
// TODO: Blend alpha
buffer[i >> 2] = bytemuck::cast(
tiny_skia::ColorU8::from_rgba(
image.data[i + 2],
image.data[i + 1],
image.data[i],
image.data[i + 3],
)
.premultiply(),
);
i += 4;
}
}
}
cosmic_text::SwashContent::SubpixelMask => {
// TODO
}
}
entry.insert((buffer, image.placement));
}
self.recently_used.insert(key);
self.entries.get(&key).map(|(buffer, placement)| {
(bytemuck::cast_slice(buffer.as_slice()), *placement)
})
}
pub fn trim(&mut self) {
if self.trim_count > Self::TRIM_INTERVAL {
self.entries
.retain(|key, _| self.recently_used.contains(key));
self.recently_used.clear();
self.trim_count = 0;
} else {
self.trim_count += 1;
}
}
}