use kas::cast::traits::*;
use kas::config::RasterConfig;
use kas::draw::{AllocError, Allocation, PassId, color::Rgba};
use kas::geom::{Quad, Vec2};
use kas_text::fonts::{self, FaceId};
use kas_text::{Effect, Glyph, GlyphId, TextDisplay};
use rustc_hash::FxHashMap as HashMap;
use swash::zeno::Format;
use crate::config::SubpixelMode;
const SCALE_STEPS: f32 = 1.0;
pub trait SpriteAllocator {
fn query_subpixel_rendering(&self) -> bool;
fn alloc_mask(&mut self, size: (u32, u32)) -> Result<Allocation, AllocError>;
fn alloc_rgba_mask(&mut self, size: (u32, u32)) -> Result<Allocation, AllocError>;
fn alloc_rgba(&mut self, size: (u32, u32)) -> Result<Allocation, AllocError>;
}
pub trait RenderQueue {
fn push_sprite(
&mut self,
pass: PassId,
glyph_pos: Vec2,
rect: Quad,
col: Rgba,
sprite: &Sprite,
);
}
kas::impl_scope! {
#[derive(Debug, PartialEq)]
#[impl_default]
pub struct Config {
subpixel_threshold: f32 = 18.0,
subpixel_x_steps: u8 = 3,
subpixel_format: Format = Format::Alpha,
}
}
enum Rasterer {
#[cfg(feature = "ab_glyph")]
AbGlyph,
Swash,
}
impl Default for Rasterer {
#[allow(clippy::needless_return, unreachable_code)]
fn default() -> Self {
return Rasterer::Swash;
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct SpriteDescriptor(u64);
impl std::fmt::Debug for SpriteDescriptor {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let dpem_steps = ((self.0 & 0x00FF_FFFF_0000_0000) >> 32) as u32;
let x_steps = ((self.0 & 0x0F00_0000_0000_0000) >> 56) as u8;
let y_steps = ((self.0 & 0xF000_0000_0000_0000) >> 60) as u8;
f.debug_struct("SpriteDescriptor")
.field("face", &self.face())
.field("glyph", &self.glyph())
.field("dpem_steps", &dpem_steps)
.field("offset_steps", &(x_steps, y_steps))
.finish()
}
}
impl SpriteDescriptor {
fn sub_pixel_x_steps(config: &Config, dpem: f32) -> u8 {
if dpem < config.subpixel_threshold {
config.subpixel_x_steps
} else {
1
}
}
pub fn new(config: &Config, face: FaceId, glyph: Glyph, dpem: f32) -> Self {
let face: u16 = face.get().cast();
let glyph_id: u16 = glyph.id.0;
let steps = Self::sub_pixel_x_steps(config, dpem);
let mult = f32::conv(steps);
let dpem = u32::conv_trunc(dpem * SCALE_STEPS + 0.5);
let x_off = u8::conv_trunc(glyph.position.0.fract() * mult) % steps;
let y_off = 0;
assert!(dpem & 0xFF00_0000 == 0 && x_off & 0xF0 == 0 && y_off & 0xF0 == 0);
let packed = face as u64
| ((glyph_id as u64) << 16)
| ((dpem as u64) << 32)
| ((x_off as u64) << 56)
| ((y_off as u64) << 60);
SpriteDescriptor(packed)
}
pub fn face(self) -> FaceId {
FaceId::from((self.0 & 0x0000_0000_0000_FFFF) as u32)
}
pub fn glyph(self) -> GlyphId {
GlyphId(((self.0 & 0x0000_0000_FFFF_0000) >> 16).cast())
}
pub fn dpem(self) -> f32 {
let dpem_steps = ((self.0 & 0x00FF_FFFF_0000_0000) >> 32) as u32;
f32::conv(dpem_steps) / SCALE_STEPS
}
pub fn fractional_position(self, config: &Config) -> (f32, f32) {
let mult = 1.0 / f32::conv(Self::sub_pixel_x_steps(config, self.dpem()));
let x_steps = ((self.0 & 0x0F00_0000_0000_0000) >> 56) as u8;
let x = f32::conv(x_steps) * mult;
let y = 0.0;
(x, y)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SpriteType {
Mask,
RgbaMask,
Bitmap,
}
#[derive(Clone, Debug, Default)]
pub struct Sprite {
pub atlas: u32,
pub ty: Option<SpriteType>,
pub size: Vec2,
pub offset: Vec2,
pub tex_quad: Quad,
}
#[derive(Debug)]
pub struct UnpreparedSprite {
pub atlas: u32,
pub ty: SpriteType,
pub origin: (u32, u32),
pub size: (u32, u32),
pub data: Vec<u8>,
}
#[derive(Default)]
pub struct State {
rasterer: Rasterer,
#[allow(unused)]
sb_align: bool,
#[allow(unused)]
hint: bool,
config: Config,
glyphs: HashMap<SpriteDescriptor, Sprite>,
prepare: Vec<UnpreparedSprite>,
scale_cx: swash::scale::ScaleContext,
}
impl State {
pub fn set_raster_config(&mut self, config: &RasterConfig) {
match config.mode {
#[cfg(feature = "ab_glyph")]
0 | 1 => self.rasterer = Rasterer::AbGlyph,
3 | 4 => self.rasterer = Rasterer::Swash,
x => log::warn!("raster mode {x} unavailable; falling back to default"),
};
self.sb_align = config.mode == 1;
self.hint = config.mode == 4;
self.config = Config {
subpixel_threshold: config.subpixel_threshold.cast(),
subpixel_x_steps: config.subpixel_x_steps.clamp(1, 16),
subpixel_format: match config.subpixel_mode {
SubpixelMode::None => Format::Alpha,
SubpixelMode::HorizontalRGB => Format::Subpixel,
},
};
}
#[inline]
pub fn unprepared_sprites(&mut self) -> &mut Vec<UnpreparedSprite> {
&mut self.prepare
}
#[cfg(feature = "ab_glyph")]
fn raster_ab_glyph(
&mut self,
allocator: &mut dyn SpriteAllocator,
face_id: FaceId,
dpem: f32,
glyphs: &mut dyn Iterator<Item = Glyph>,
) {
use ab_glyph::Font;
let face_store = fonts::library().get_face_store(face_id);
for glyph in glyphs {
let desc = SpriteDescriptor::new(&self.config, face_id, glyph, dpem);
if self.glyphs.contains_key(&desc) {
continue;
}
let (mut x, y) = desc.fractional_position(&self.config);
if self.sb_align && desc.dpem(&self.config) >= self.config.subpixel_threshold {
let sf = face_store.face_ref().scale_by_dpem(dpem);
x -= sf.h_side_bearing(glyph.id);
}
let font = face_store.ab_glyph();
let scale = dpem * font.height_unscaled() / font.units_per_em().unwrap();
let glyph = ab_glyph::Glyph {
id: ab_glyph::GlyphId(glyph.id.0),
scale: scale.into(),
position: ab_glyph::point(x, y),
};
let Some(outline) = font.outline_glyph(glyph) else {
log::warn!("raster_glyphs failed: unable to outline glyph");
self.glyphs.insert(desc, Sprite::default());
continue;
};
let bounds = outline.px_bounds();
let offset: (i32, i32) = (bounds.min.x.cast_trunc(), bounds.min.y.cast_trunc());
let size = bounds.max - bounds.min;
let size = (u32::conv_trunc(size.x), u32::conv_trunc(size.y));
if size.0 == 0 || size.1 == 0 {
self.glyphs.insert(desc, Sprite::default());
continue;
}
let mut data = vec![0; usize::conv(size.0 * size.1)];
outline.draw(|x, y, c| {
data[usize::conv((y * size.0) + x)] = (c * 256.0) as u8;
});
let Ok(alloc) = allocator.alloc_a(size) else {
log::warn!("raster_glyphs failed: unable to allocate");
self.glyphs.insert(desc, Sprite::default());
continue;
};
self.prepare.push(UnpreparedSprite {
atlas: alloc.atlas,
color: false,
origin: alloc.origin,
size,
data,
});
self.glyphs.insert(desc, Sprite {
atlas: alloc.atlas,
color: false,
valid: true,
size: Vec2(size.0.cast(), size.1.cast()),
offset: Vec2(offset.0.cast(), offset.1.cast()),
tex_quad: alloc.tex_quad,
});
}
}
fn raster_swash(
&mut self,
allocator: &mut dyn SpriteAllocator,
face_id: FaceId,
dpem: f32,
glyphs: &mut dyn Iterator<Item = Glyph>,
) {
use swash::scale::{Render, Source, StrikeWith, image::Content};
use swash::zeno::{Angle, Transform};
let face = fonts::library().get_face_store(face_id);
let font = face.swash();
let synthesis = face.synthesis();
let mut scaler = self
.scale_cx
.builder(font)
.size(dpem)
.hint(self.hint)
.variations(
synthesis
.variation_settings()
.iter()
.map(|(tag, value)| (swash::tag_from_bytes(&tag.to_be_bytes()), *value)),
)
.build();
let sources = &[
Source::ColorOutline(0),
Source::ColorBitmap(StrikeWith::BestFit),
Source::Bitmap(StrikeWith::BestFit),
Source::Outline,
];
let mut format = self.config.subpixel_format;
if format != Format::Alpha && allocator.query_subpixel_rendering() == false {
format = Format::Alpha;
}
let transform = synthesis
.skew()
.map(|angle| Transform::skew(Angle::from_degrees(angle), Angle::ZERO));
let embolden = if synthesis.embolden() { dpem * 0.02 } else { 0.0 };
for glyph in glyphs {
let desc = SpriteDescriptor::new(&self.config, face_id, glyph, dpem);
if self.glyphs.contains_key(&desc) {
continue;
}
let Some(image) = Render::new(sources)
.format(format)
.offset(desc.fractional_position(&self.config).into())
.transform(transform)
.embolden(embolden)
.render(&mut scaler, desc.glyph().0)
else {
log::warn!("raster_glyphs failed: unable to construct renderer");
self.glyphs.insert(desc, Sprite::default());
continue;
};
let offset = (image.placement.left, -image.placement.top);
let size = (image.placement.width, image.placement.height);
if size.0 == 0 || size.1 == 0 {
self.glyphs.insert(desc, Sprite::default());
continue;
}
let sprite = match image.content {
Content::Mask => {
let Ok(alloc) = allocator.alloc_mask(size) else {
log::warn!("raster_glyphs failed: unable to allocate");
self.glyphs.insert(desc, Sprite::default());
continue;
};
self.prepare.push(UnpreparedSprite {
atlas: alloc.atlas,
ty: SpriteType::Mask,
origin: alloc.origin,
size,
data: image.data,
});
Sprite {
atlas: alloc.atlas,
ty: Some(SpriteType::Mask),
size: Vec2(size.0.cast(), size.1.cast()),
offset: Vec2(offset.0.cast(), offset.1.cast()),
tex_quad: alloc.tex_quad,
}
}
Content::SubpixelMask => {
let Ok(alloc) = allocator.alloc_rgba_mask(size) else {
log::warn!("raster_glyphs failed: unable to allocate");
self.glyphs.insert(desc, Sprite::default());
continue;
};
self.prepare.push(UnpreparedSprite {
atlas: alloc.atlas,
ty: SpriteType::RgbaMask,
origin: alloc.origin,
size,
data: image.data,
});
Sprite {
atlas: alloc.atlas,
ty: Some(SpriteType::RgbaMask),
size: Vec2(size.0.cast(), size.1.cast()),
offset: Vec2(offset.0.cast(), offset.1.cast()),
tex_quad: alloc.tex_quad,
}
}
Content::Color => {
let Ok(alloc) = allocator.alloc_rgba(size) else {
log::warn!("raster_glyphs failed: unable to allocate");
self.glyphs.insert(desc, Sprite::default());
continue;
};
self.prepare.push(UnpreparedSprite {
atlas: alloc.atlas,
ty: SpriteType::Bitmap,
origin: alloc.origin,
size,
data: image.data,
});
Sprite {
atlas: alloc.atlas,
ty: Some(SpriteType::Bitmap),
size: Vec2(size.0.cast(), size.1.cast()),
offset: Vec2(offset.0.cast(), offset.1.cast()),
tex_quad: alloc.tex_quad,
}
}
};
self.glyphs.insert(desc, sprite);
}
}
#[inline]
pub fn raster_glyphs(
&mut self,
allocator: &mut dyn SpriteAllocator,
face_id: FaceId,
dpem: f32,
mut glyphs: impl Iterator<Item = Glyph>,
) {
match self.rasterer {
#[cfg(feature = "ab_glyph")]
Rasterer::AbGlyph => self.raster_ab_glyph(allocator, face_id, dpem, &mut glyphs),
Rasterer::Swash => self.raster_swash(allocator, face_id, dpem, &mut glyphs),
}
}
pub fn text(
&mut self,
allocator: &mut dyn SpriteAllocator,
queue: &mut dyn RenderQueue,
pass: PassId,
pos: Vec2,
bb: Quad,
text: &TextDisplay,
col: Rgba,
) {
for run in text.runs(pos.into(), &[]) {
let face = run.face_id();
let dpem = run.dpem();
for glyph in run.glyphs() {
let desc = SpriteDescriptor::new(&self.config, face, glyph, dpem);
let sprite = match self.glyphs.get(&desc) {
Some(sprite) => sprite,
None => {
self.raster_glyphs(allocator, face, dpem, run.glyphs());
match self.glyphs.get(&desc) {
Some(sprite) => sprite,
None => continue,
}
}
};
queue.push_sprite(pass, Vec2::from(glyph.position), bb, col, sprite);
}
}
}
#[allow(clippy::too_many_arguments)]
pub fn text_effects(
&mut self,
allocator: &mut dyn SpriteAllocator,
queue: &mut dyn RenderQueue,
pass: PassId,
pos: Vec2,
bb: Quad,
text: &TextDisplay,
colors: &[Rgba],
effects: &[Effect],
mut draw_quad: impl FnMut(Quad, Rgba),
) {
if effects.len() <= 1
&& effects
.first()
.map(|e| e.flags == Default::default())
.unwrap_or(true)
{
let col = colors.first().cloned().unwrap_or(Rgba::BLACK);
self.text(allocator, queue, pass, pos, bb, text, col);
return;
}
for run in text.runs(pos.into(), effects) {
let face = run.face_id();
let dpem = run.dpem();
let for_glyph = |glyph: Glyph, e: u16| {
let desc = SpriteDescriptor::new(&self.config, face, glyph, dpem);
let sprite = match self.glyphs.get(&desc) {
Some(sprite) => sprite,
None => {
self.raster_glyphs(allocator, face, dpem, run.glyphs());
match self.glyphs.get(&desc) {
Some(sprite) => sprite,
None => return,
}
}
};
let col = colors.get(usize::conv(e)).cloned().unwrap_or(Rgba::BLACK);
queue.push_sprite(pass, glyph.position.into(), bb, col, sprite);
};
let for_rect = |x1, x2, y: f32, h: f32, e: u16| {
let y = y.ceil();
let y2 = y + h.ceil();
if let Some(quad) = Quad::from_coords(Vec2(x1, y), Vec2(x2, y2)).intersection(&bb) {
let col = colors.get(usize::conv(e)).cloned().unwrap_or(Rgba::BLACK);
draw_quad(quad, col);
}
};
run.glyphs_with_effects(for_glyph, for_rect);
}
}
}