2022-10-25 14:42:26 -06:00
|
|
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
|
|
|
|
|
2022-10-25 10:55:24 -06:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
use swash::scale::{ScaleContext, image::Content};
|
|
|
|
|
use swash::scale::{Render, Source, StrikeWith};
|
|
|
|
|
use swash::zeno::{Format, Vector};
|
|
|
|
|
|
2022-10-27 09:07:47 -06:00
|
|
|
use crate::{CacheKey, Color, FontSystem};
|
2022-10-25 10:55:24 -06:00
|
|
|
|
2022-10-25 23:52:06 -02:30
|
|
|
pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
|
2022-10-25 10:55:24 -06:00
|
|
|
|
2022-10-26 12:23:03 -06:00
|
|
|
fn swash_image<'a>(font_system: &'a FontSystem<'a>, context: &mut ScaleContext, cache_key: CacheKey) -> Option<SwashImage> {
|
|
|
|
|
let font = match font_system.get_font(cache_key.font_id) {
|
2022-10-25 19:40:17 -06:00
|
|
|
Some(some) => some,
|
|
|
|
|
None => {
|
|
|
|
|
log::warn!("did not find font {:?}", cache_key.font_id);
|
|
|
|
|
return None;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Build the scaler
|
|
|
|
|
let mut scaler = context
|
|
|
|
|
.builder(font.as_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)
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-27 09:56:53 -06:00
|
|
|
/// Cache for rasterizing with the swash scaler
|
2022-10-26 12:23:03 -06:00
|
|
|
pub struct SwashCache<'a> {
|
|
|
|
|
font_system: &'a FontSystem<'a>,
|
2022-10-25 10:55:24 -06:00
|
|
|
context: ScaleContext,
|
2022-10-25 12:52:46 -06:00
|
|
|
pub image_cache: HashMap<CacheKey, Option<SwashImage>>,
|
2022-10-25 10:55:24 -06:00
|
|
|
}
|
|
|
|
|
|
2022-10-26 12:23:03 -06:00
|
|
|
impl<'a> SwashCache<'a> {
|
2022-10-25 10:55:24 -06:00
|
|
|
/// Create a new swash cache
|
2022-10-26 12:23:03 -06:00
|
|
|
pub fn new(font_system: &'a FontSystem<'a>) -> Self {
|
2022-10-25 10:55:24 -06:00
|
|
|
Self {
|
2022-10-26 12:23:03 -06:00
|
|
|
font_system: font_system,
|
2022-10-25 10:55:24 -06:00
|
|
|
context: ScaleContext::new(),
|
2022-10-25 12:52:46 -06:00
|
|
|
image_cache: HashMap::new()
|
2022-10-25 10:55:24 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-25 19:40:17 -06:00
|
|
|
/// Create a swash Image from a cache key, without caching results
|
2022-10-26 12:23:03 -06:00
|
|
|
pub fn get_image_uncached(&mut self, cache_key: CacheKey) -> Option<SwashImage> {
|
|
|
|
|
swash_image(self.font_system, &mut self.context, cache_key)
|
2022-10-25 19:40:17 -06:00
|
|
|
}
|
|
|
|
|
|
2022-10-25 10:55:24 -06:00
|
|
|
/// Create a swash Image from a cache key, caching results
|
2022-10-26 12:23:03 -06:00
|
|
|
pub fn get_image(&mut self, cache_key: CacheKey) -> &Option<SwashImage> {
|
2022-10-25 12:52:46 -06:00
|
|
|
self.image_cache.entry(cache_key).or_insert_with(|| {
|
2022-10-26 12:23:03 -06:00
|
|
|
swash_image(self.font_system, &mut self.context, cache_key)
|
2022-10-25 11:40:10 -06:00
|
|
|
})
|
2022-10-25 10:55:24 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Enumerate pixels in an Image, use `with_image` for better performance
|
2022-10-27 09:07:47 -06:00
|
|
|
pub fn with_pixels<F: FnMut(i32, i32, Color)>(
|
2022-10-25 10:55:24 -06:00
|
|
|
&mut self,
|
|
|
|
|
cache_key: CacheKey,
|
2022-10-27 09:07:47 -06:00
|
|
|
base: Color,
|
2022-10-25 10:55:24 -06:00
|
|
|
mut f: F
|
|
|
|
|
) {
|
2022-10-26 12:23:03 -06:00
|
|
|
if let Some(image) = self.get_image(cache_key) {
|
2022-10-25 11:40:10 -06:00
|
|
|
let x = image.placement.left;
|
|
|
|
|
let y = -image.placement.top;
|
2022-10-25 10:55:24 -06:00
|
|
|
|
2022-10-25 11:40:10 -06:00
|
|
|
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?
|
2022-10-27 09:07:47 -06:00
|
|
|
f(
|
|
|
|
|
x + off_x,
|
|
|
|
|
y + off_y,
|
|
|
|
|
Color(
|
|
|
|
|
((image.data[i] as u32) << 24) |
|
|
|
|
|
base.0 & 0xFFFFFF
|
|
|
|
|
)
|
|
|
|
|
);
|
2022-10-25 11:40:10 -06:00
|
|
|
i += 1;
|
2022-10-25 10:55:24 -06:00
|
|
|
}
|
|
|
|
|
}
|
2022-10-25 11:40:10 -06:00
|
|
|
}
|
|
|
|
|
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?
|
2022-10-27 09:07:47 -06:00
|
|
|
f(
|
|
|
|
|
x + off_x,
|
|
|
|
|
y + off_y,
|
|
|
|
|
Color::rgba(
|
|
|
|
|
image.data[i + 2],
|
|
|
|
|
image.data[i + 1],
|
|
|
|
|
image.data[i],
|
|
|
|
|
image.data[i + 3]
|
|
|
|
|
)
|
|
|
|
|
);
|
2022-10-25 11:40:10 -06:00
|
|
|
i += 4;
|
2022-10-25 10:55:24 -06:00
|
|
|
}
|
|
|
|
|
}
|
2022-10-25 11:40:10 -06:00
|
|
|
}
|
|
|
|
|
Content::SubpixelMask => {
|
|
|
|
|
log::warn!("TODO: SubpixelMask");
|
2022-10-25 10:55:24 -06:00
|
|
|
}
|
|
|
|
|
}
|
2022-10-25 11:40:10 -06:00
|
|
|
}
|
2022-10-25 10:55:24 -06:00
|
|
|
}
|
|
|
|
|
}
|