use crate::fixed::F26Dot6;
use crate::grayscale::GrayscaleLevel;
use crate::scan_converter::ScanConverter;
use crate::{DropoutMode, FillRule};
use read_fonts::FontRef as ReadFontsRef;
use skrifa::instance::Size;
use skrifa::outline::DrawSettings;
use skrifa::{GlyphId as SkrifaGlyphId, MetadataProvider};
pub struct GlyphRasterizer<'a> {
font: ReadFontsRef<'a>,
size: f32,
oversample: u8,
location: skrifa::instance::Location,
}
impl<'a> GlyphRasterizer<'a> {
pub fn new(font_data: &'a [u8], size: f32) -> Result<Self, String> {
let font =
ReadFontsRef::new(font_data).map_err(|e| format!("Failed to parse font: {}", e))?;
Ok(Self {
font,
size,
oversample: 4, location: skrifa::instance::Location::default(),
})
}
pub fn set_variations(&mut self, variations: &[(String, f32)]) -> Result<(), String> {
if variations.is_empty() {
self.location = skrifa::instance::Location::default();
return Ok(());
}
let axes = self.font.axes();
let settings: Vec<(&str, f32)> = variations
.iter()
.map(|(tag, value)| (tag.as_str(), *value))
.collect();
self.location = axes.location(settings);
Ok(())
}
pub fn with_oversample(mut self, oversample: u8) -> Self {
self.oversample = oversample.max(1);
self
}
pub fn render_glyph(
&self,
glyph_id: u32,
fill_rule: FillRule,
dropout_mode: DropoutMode,
) -> Result<GlyphBitmap, String> {
let skrifa_gid = SkrifaGlyphId::new(glyph_id);
let outline_glyphs = self.font.outline_glyphs();
let glyph = outline_glyphs
.get(skrifa_gid)
.ok_or_else(|| format!("Glyph {} not found", glyph_id))?;
struct BoundsCalculator {
x_min: f32,
y_min: f32,
x_max: f32,
y_max: f32,
has_points: bool,
}
impl BoundsCalculator {
fn new() -> Self {
Self {
x_min: f32::MAX,
y_min: f32::MAX,
x_max: f32::MIN,
y_max: f32::MIN,
has_points: false,
}
}
fn update(&mut self, x: f32, y: f32) {
self.x_min = self.x_min.min(x);
self.y_min = self.y_min.min(y);
self.x_max = self.x_max.max(x);
self.y_max = self.y_max.max(y);
self.has_points = true;
}
}
impl skrifa::outline::OutlinePen for BoundsCalculator {
fn move_to(&mut self, x: f32, y: f32) {
self.update(x, y);
}
fn line_to(&mut self, x: f32, y: f32) {
self.update(x, y);
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.update(x1, y1);
self.update(x, y);
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.update(x1, y1);
self.update(x2, y2);
self.update(x, y);
}
fn close(&mut self) {}
}
let size_setting = Size::new(self.size);
let location_ref = self.location.coords();
let draw_settings = DrawSettings::unhinted(size_setting, location_ref);
let mut bounds_calc = BoundsCalculator::new();
glyph
.draw(draw_settings, &mut bounds_calc)
.map_err(|e| format!("Failed to calculate bounds: {:?}", e))?;
if !bounds_calc.has_points {
return Ok(GlyphBitmap {
width: 0,
height: 0,
left: 0,
top: 0,
data: Vec::new(),
});
}
let x_min = bounds_calc.x_min.floor() as i32;
let y_min = bounds_calc.y_min.floor() as i32;
let x_max = bounds_calc.x_max.ceil() as i32;
let y_max = bounds_calc.y_max.ceil() as i32;
let out_width = ((x_max - x_min) as usize).max(1);
let out_height = ((y_max - y_min) as usize).max(1);
let width = out_width * self.oversample as usize;
let height = out_height * self.oversample as usize;
if width > 4096 || height > 4096 {
return Err(format!(
"Glyph bitmap too large: {}x{} (max 4096x4096)",
width, height
));
}
let mut scan_converter = ScanConverter::new(width, height);
scan_converter.set_fill_rule(fill_rule);
scan_converter.set_dropout_mode(dropout_mode);
let oversample_scale = self.oversample as f32;
let x_offset = -x_min as f32 * self.oversample as f32;
let y_offset = y_max as f32 * self.oversample as f32;
struct TransformPen<'p> {
inner: &'p mut ScanConverter,
scale: f32,
x_offset: f32,
y_offset: f32,
}
impl<'p> skrifa::outline::OutlinePen for TransformPen<'p> {
fn move_to(&mut self, x: f32, y: f32) {
let tx = x * self.scale + self.x_offset;
let ty = -y * self.scale + self.y_offset; self.inner
.move_to(F26Dot6::from_float(tx), F26Dot6::from_float(ty));
}
fn line_to(&mut self, x: f32, y: f32) {
let tx = x * self.scale + self.x_offset;
let ty = -y * self.scale + self.y_offset; self.inner
.line_to(F26Dot6::from_float(tx), F26Dot6::from_float(ty));
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
let tx1 = x1 * self.scale + self.x_offset;
let ty1 = -y1 * self.scale + self.y_offset; let tx = x * self.scale + self.x_offset;
let ty = -y * self.scale + self.y_offset; self.inner.quadratic_to(
F26Dot6::from_float(tx1),
F26Dot6::from_float(ty1),
F26Dot6::from_float(tx),
F26Dot6::from_float(ty),
);
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
let tx1 = x1 * self.scale + self.x_offset;
let ty1 = -y1 * self.scale + self.y_offset; let tx2 = x2 * self.scale + self.x_offset;
let ty2 = -y2 * self.scale + self.y_offset; let tx = x * self.scale + self.x_offset;
let ty = -y * self.scale + self.y_offset; self.inner.cubic_to(
F26Dot6::from_float(tx1),
F26Dot6::from_float(ty1),
F26Dot6::from_float(tx2),
F26Dot6::from_float(ty2),
F26Dot6::from_float(tx),
F26Dot6::from_float(ty),
);
}
fn close(&mut self) {
self.inner.close();
}
}
let mut transform_pen = TransformPen {
inner: &mut scan_converter,
scale: oversample_scale,
x_offset,
y_offset,
};
let size_setting = Size::new(self.size);
let location_ref = self.location.coords(); let draw_settings = DrawSettings::unhinted(size_setting, location_ref);
glyph
.draw(draw_settings, &mut transform_pen)
.map_err(|e| format!("Failed to draw outline: {:?}", e))?;
let grayscale_level = match self.oversample {
2 => GrayscaleLevel::Level2x2,
4 => GrayscaleLevel::Level4x4,
8 => GrayscaleLevel::Level8x8,
_ => GrayscaleLevel::Level4x4, };
let gray_bitmap = crate::grayscale::render_grayscale(
&mut scan_converter,
out_width,
out_height,
grayscale_level,
);
Ok(GlyphBitmap {
width: out_width as u32,
height: out_height as u32,
left: x_min,
top: y_max, data: gray_bitmap,
})
}
}
#[derive(Debug, Clone)]
pub struct GlyphBitmap {
pub width: u32,
pub height: u32,
pub left: i32,
pub top: i32,
pub data: Vec<u8>,
}
#[cfg(test)]
mod tests {
}