use crate::cache::{subpixel_offset, subpixel_slot, CachedGlyph, GlyphCache, GlyphKey};
use crate::face::Face;
use crate::face_chain::{shear_for, FaceChain};
use crate::rasterizer::Rasterizer;
use crate::shaper::PositionedGlyph;
use crate::stroke::{dilate_alpha, dilate_offset};
use crate::style::{synthetic_bold_radius, Style};
use crate::{Error, Rgba};
#[derive(Debug, Clone, Default)]
pub struct RgbaBitmap {
pub width: u32,
pub height: u32,
pub data: Vec<u8>,
}
impl RgbaBitmap {
pub fn new(width: u32, height: u32) -> Self {
Self {
width,
height,
data: vec![0; (width as usize) * (height as usize) * 4],
}
}
pub fn stride(&self) -> usize {
(self.width as usize) * 4
}
pub fn is_empty(&self) -> bool {
self.width == 0 || self.height == 0
}
pub fn get(&self, x: u32, y: u32) -> [u8; 4] {
if x >= self.width || y >= self.height {
return [0; 4];
}
let off = ((y as usize) * (self.width as usize) + (x as usize)) * 4;
[
self.data[off],
self.data[off + 1],
self.data[off + 2],
self.data[off + 3],
]
}
pub fn nonzero_alpha_count(&self) -> usize {
self.data.chunks_exact(4).filter(|p| p[3] != 0).count()
}
}
#[derive(Debug, Default)]
pub struct Composer {
cache: GlyphCache,
}
impl Composer {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(cap: usize) -> Self {
Self {
cache: GlyphCache::new(cap),
}
}
pub fn cache(&self) -> &GlyphCache {
&self.cache
}
pub fn cache_mut(&mut self) -> &mut GlyphCache {
&mut self.cache
}
#[allow(clippy::too_many_arguments)]
pub fn compose_run(
&mut self,
glyphs: &[PositionedGlyph],
face: &Face,
size_px: f32,
color: Rgba,
dst: &mut RgbaBitmap,
x: f32,
y: f32,
) -> Result<(), Error> {
let face_ref = SingleFace::Borrowed(face);
self.compose_run_inner(
glyphs,
ChainRef::Single(face_ref),
size_px,
Style::REGULAR,
color,
None,
dst,
x,
y,
)
}
#[allow(clippy::too_many_arguments)]
pub fn compose_run_styled(
&mut self,
glyphs: &[PositionedGlyph],
chain: &FaceChain,
size_px: f32,
style: Style,
color: Rgba,
dst: &mut RgbaBitmap,
x: f32,
y: f32,
) -> Result<(), Error> {
self.compose_run_inner(
glyphs,
ChainRef::Chain(chain),
size_px,
style,
color,
None,
dst,
x,
y,
)
}
#[allow(clippy::too_many_arguments)]
pub fn compose_run_with_stroke(
&mut self,
glyphs: &[PositionedGlyph],
chain: &FaceChain,
size_px: f32,
style: Style,
fill_color: Rgba,
stroke: Option<StrokeStyle>,
dst: &mut RgbaBitmap,
x: f32,
y: f32,
) -> Result<(), Error> {
if let Some(s) = stroke {
if s.width_px > 0.0 && s.color[3] > 0 {
self.compose_run_inner(
glyphs,
ChainRef::Chain(chain),
size_px,
style,
s.color,
Some(s.width_px),
dst,
x,
y,
)?;
}
}
self.compose_run_inner(
glyphs,
ChainRef::Chain(chain),
size_px,
style,
fill_color,
None,
dst,
x,
y,
)
}
#[allow(clippy::too_many_arguments)]
fn compose_run_inner(
&mut self,
glyphs: &[PositionedGlyph],
chain: ChainRef<'_>,
size_px: f32,
style: Style,
color: Rgba,
dilate_radius_px: Option<f32>,
dst: &mut RgbaBitmap,
x: f32,
y: f32,
) -> Result<(), Error> {
if dst.is_empty() || glyphs.is_empty() {
return Ok(());
}
let mut pen_x = x;
let pen_y = y;
for g in glyphs {
let face = chain.face(g.face_idx);
let shear = shear_for(face, style);
let target_x = pen_x + g.x_offset;
let int_x = target_x.floor();
let frac_x = target_x - int_x;
let slot = subpixel_slot(frac_x);
let key = GlyphKey::new_subpixel(face.id(), g.glyph_id, size_px, shear, slot);
let cached = if let Some(c) = self.cache.get(&key) {
c
} else {
let sub_x = subpixel_offset(slot);
let bitmap =
Rasterizer::raster_glyph_subpixel(face, g.glyph_id, size_px, shear, sub_x)?;
let (off_x, off_y) =
Rasterizer::glyph_offset_subpixel(face, g.glyph_id, size_px, shear, sub_x)?;
let entry = CachedGlyph {
bitmap,
offset_x: off_x,
offset_y: off_y,
};
self.cache.insert(key, entry.clone());
entry
};
let bold_r = synthetic_bold_radius(style, face.weight_class(), size_px);
let total_r = dilate_radius_px.unwrap_or(0.0) + bold_r;
let (blit_bitmap, blit_dx, blit_dy) = if total_r > 0.0 {
let dil = dilate_alpha(&cached.bitmap, total_r);
let off = dilate_offset(total_r) as f32;
(dil, -off, -off)
} else {
(cached.bitmap.clone(), 0.0, 0.0)
};
let glyph_x = int_x + cached.offset_x + blit_dx;
let glyph_y = pen_y + g.y_offset + cached.offset_y + blit_dy;
if !blit_bitmap.is_empty() {
let dw = dst.width;
let dh = dst.height;
let ds = dst.stride();
oxideav_pixfmt::blit_alpha_mask(
&mut dst.data,
dw,
dh,
ds,
glyph_x.round() as i32,
glyph_y.round() as i32,
&blit_bitmap.data,
blit_bitmap.width,
blit_bitmap.height,
blit_bitmap.width as usize,
color,
);
}
pen_x += g.x_advance;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct StrokeStyle {
pub width_px: f32,
pub color: Rgba,
}
impl StrokeStyle {
pub fn new(width_px: f32, color: Rgba) -> Self {
Self { width_px, color }
}
}
enum SingleFace<'a> {
Borrowed(&'a Face),
}
enum ChainRef<'a> {
Single(SingleFace<'a>),
Chain(&'a FaceChain),
}
impl<'a> ChainRef<'a> {
fn face(&self, idx: u16) -> &Face {
match self {
ChainRef::Single(SingleFace::Borrowed(f)) => f,
ChainRef::Chain(c) => c.face(idx),
}
}
}