cosmic-text/src/swash.rs

200 lines
6.1 KiB
Rust
Raw Normal View History

2022-10-25 14:42:26 -06:00
// SPDX-License-Identifier: MIT OR Apache-2.0
2022-11-08 08:43:27 -07:00
#[cfg(not(feature = "std"))]
use alloc::collections::BTreeMap as Map;
2022-12-15 14:31:19 -07:00
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::fmt;
2022-11-08 08:43:27 -07:00
#[cfg(feature = "std")]
use std::collections::HashMap as Map;
2023-01-04 20:03:03 -07:00
use swash::scale::{image::Content, ScaleContext};
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 23:52:06 -02:30
pub use swash::scale::image::{Content as SwashContent, Image as SwashImage};
pub use swash::zeno::{Command, Placement};
2023-01-04 20:03:03 -07:00
fn swash_image(
2023-03-12 10:30:20 +01:00
font_system: &mut FontSystem,
2023-01-04 20:03:03 -07:00
context: &mut ScaleContext,
cache_key: CacheKey,
) -> Option<SwashImage> {
let font = match font_system.get_font(cache_key.font_id) {
Some(some) => some,
None => {
log::warn!("did not find font {:?}", cache_key.font_id);
return None;
2023-01-04 20:03:03 -07:00
}
};
// Build the scaler
let mut scaler = context
.builder(font.as_swash())
.size(f32::from_bits(cache_key.font_size_bits))
.hint(true)
.build();
// Compute the fractional offset-- you'll likely want to quantize this
// in a real renderer
2023-01-04 20:03:03 -07:00
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)
}
2023-01-04 20:03:03 -07:00
fn swash_outline_commands(
2023-03-12 10:30:20 +01:00
font_system: &mut FontSystem,
2023-01-04 20:03:03 -07:00
context: &mut ScaleContext,
cache_key: CacheKey,
) -> Option<Vec<swash::zeno::Command>> {
use swash::zeno::PathData as _;
let font = match font_system.get_font(cache_key.font_id) {
Some(some) => some,
None => {
log::warn!("did not find font {:?}", cache_key.font_id);
return None;
2023-01-04 20:03:03 -07:00
}
};
// Build the scaler
let mut scaler = context
.builder(font.as_swash())
.size(f32::from_bits(cache_key.font_size_bits))
.build();
// Scale the outline
2023-01-04 20:03:03 -07:00
let outline = scaler
.scale_outline(cache_key.glyph_id)
.or_else(|| scaler.scale_color_outline(cache_key.glyph_id))?;
// Get the path information of the outline
let path = outline.path();
// Return the commands
Some(path.commands().collect())
}
/// Cache for rasterizing with the swash scaler
2023-03-01 22:41:59 +01:00
pub struct SwashCache {
context: ScaleContext,
2022-11-08 08:43:27 -07:00
pub image_cache: Map<CacheKey, Option<SwashImage>>,
pub outline_command_cache: Map<CacheKey, Option<Vec<swash::zeno::Command>>>,
}
impl fmt::Debug for SwashCache {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("SwashCache { .. }")
}
}
2023-03-01 22:41:59 +01:00
impl SwashCache {
/// Create a new swash cache
2023-03-01 22:41:59 +01:00
pub fn new() -> Self {
Self {
context: ScaleContext::new(),
image_cache: Map::new(),
2023-01-04 20:03:03 -07:00
outline_command_cache: Map::new(),
}
}
/// Create a swash Image from a cache key, without caching results
2023-03-01 22:41:59 +01:00
pub fn get_image_uncached(
&mut self,
2023-03-12 10:30:20 +01:00
font_system: &mut FontSystem,
2023-03-01 22:41:59 +01:00
cache_key: CacheKey,
) -> Option<SwashImage> {
swash_image(font_system, &mut self.context, cache_key)
}
/// Create a swash Image from a cache key, caching results
2023-03-01 22:41:59 +01:00
pub fn get_image(
&mut self,
2023-03-12 10:30:20 +01:00
font_system: &mut FontSystem,
2023-03-01 22:41:59 +01:00
cache_key: CacheKey,
) -> &Option<SwashImage> {
2023-01-04 20:03:03 -07:00
self.image_cache
.entry(cache_key)
2023-03-01 22:41:59 +01:00
.or_insert_with(|| swash_image(font_system, &mut self.context, cache_key))
}
2023-03-01 22:41:59 +01:00
pub fn get_outline_commands(
&mut self,
2023-03-12 10:30:20 +01:00
font_system: &mut FontSystem,
2023-03-01 22:41:59 +01:00
cache_key: CacheKey,
) -> Option<&[swash::zeno::Command]> {
2023-01-04 20:03:03 -07:00
self.outline_command_cache
.entry(cache_key)
2023-03-01 22:41:59 +01:00
.or_insert_with(|| swash_outline_commands(font_system, &mut self.context, cache_key))
2023-01-04 20:03:03 -07:00
.as_deref()
}
/// 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)>(
&mut self,
2023-03-12 10:30:20 +01:00
font_system: &mut FontSystem,
cache_key: CacheKey,
2022-10-27 09:07:47 -06:00
base: Color,
2023-01-04 20:03:03 -07:00
mut f: F,
) {
2023-03-01 22:41:59 +01:00
if let Some(image) = self.get_image(font_system, cache_key) {
2022-10-25 11:40:10 -06:00
let x = image.placement.left;
let y = -image.placement.top;
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,
2023-01-04 20:03:03 -07:00
Color(((image.data[i] as u32) << 24) | base.0 & 0xFF_FF_FF),
2022-10-27 09:07:47 -06:00
);
2022-10-25 11:40:10 -06:00
i += 1;
}
}
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],
2022-10-27 18:16:42 -06:00
image.data[i + 1],
image.data[i + 2],
2023-01-04 20:03:03 -07:00
image.data[i + 3],
),
2022-10-27 09:07:47 -06:00
);
2022-10-25 11:40:10 -06:00
i += 4;
}
}
2022-10-25 11:40:10 -06:00
}
Content::SubpixelMask => {
log::warn!("TODO: SubpixelMask");
}
}
2022-10-25 11:40:10 -06:00
}
}
}