#![deny(missing_debug_implementations)]
#![warn(rust_2018_idioms)]
pub mod cache;
pub mod color;
pub mod compose;
pub mod face;
pub mod face_chain;
pub mod layout;
pub mod outline;
pub mod rasterizer;
pub mod shaper;
pub mod stroke;
pub mod style;
pub use cache::{
subpixel_offset, subpixel_slot, CachedGlyph, GlyphCache, GlyphKey, SUBPIXEL_STEPS,
};
pub use color::{Rgba, TRANSPARENT, WHITE};
pub use compose::{Composer, RgbaBitmap, StrokeStyle};
pub use face::{Face, FaceKind};
pub use face_chain::{shear_for, FaceChain};
pub use layout::{run_width, wrap_lines};
pub use outline::{
flatten, flatten_cubic, flatten_cubic_with_shear, flatten_with_shear,
flatten_with_shear_offset, FlatBounds, FlatOutline,
};
pub use rasterizer::{AlphaBitmap, Rasterizer};
pub use shaper::{PositionedGlyph, Shaper};
pub use stroke::{dilate_alpha, dilate_offset};
pub use style::{
synthetic_bold_radius, synthetic_italic_shear, Style, DEFAULT_SYNTHETIC_ITALIC_DEG,
ITALIC_ANGLE_EPSILON_DEG, SYNTHETIC_BOLD_PX_PER_WEIGHT_STEP_PER_PX, SYNTHETIC_BOLD_THRESHOLD,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
Ttf(oxideav_ttf::Error),
Otf(oxideav_otf::Error),
InvalidSize,
WrongFaceKind {
expected: FaceKind,
actual: FaceKind,
},
}
impl From<oxideav_ttf::Error> for Error {
fn from(e: oxideav_ttf::Error) -> Self {
Self::Ttf(e)
}
}
impl From<oxideav_otf::Error> for Error {
fn from(e: oxideav_otf::Error) -> Self {
Self::Otf(e)
}
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Ttf(e) => write!(f, "ttf error: {e}"),
Self::Otf(e) => write!(f, "otf error: {e}"),
Self::InvalidSize => f.write_str("non-positive font size"),
Self::WrongFaceKind { expected, actual } => {
write!(f, "wrong face kind: expected {expected:?}, got {actual:?}")
}
}
}
}
impl std::error::Error for Error {}
pub fn render_text(
face: &Face,
text: &str,
size_px: f32,
color: Rgba,
) -> Result<RgbaBitmap, Error> {
render_text_styled(face, text, size_px, color, Style::REGULAR)
}
pub fn render_text_styled(
face: &Face,
text: &str,
size_px: f32,
color: Rgba,
style: Style,
) -> Result<RgbaBitmap, Error> {
if size_px <= 0.0 || !size_px.is_finite() {
return Err(Error::InvalidSize);
}
let glyphs = Shaper::shape(face, text, size_px)?;
if glyphs.is_empty() {
return Ok(RgbaBitmap::default());
}
let shear = synthetic_italic_shear(style, face.italic_angle());
let bold_r = crate::style::synthetic_bold_radius(style, face.weight_class(), size_px);
let mut pen = 0.0_f32;
let mut x_min = f32::INFINITY;
let mut y_min = f32::INFINITY;
let mut x_max = f32::NEG_INFINITY;
let mut y_max = f32::NEG_INFINITY;
let mut prepared: Vec<(PositionedGlyph, AlphaBitmap, f32, f32, f32)> =
Vec::with_capacity(glyphs.len());
for g in &glyphs {
let target = pen + g.x_offset;
let int_x = target.floor();
let frac_x = target - int_x;
let slot = crate::cache::subpixel_slot(frac_x);
let sub_x = crate::cache::subpixel_offset(slot);
let mut bitmap =
Rasterizer::raster_glyph_subpixel(face, g.glyph_id, size_px, shear, sub_x)?;
let (mut off_x, mut off_y) =
Rasterizer::glyph_offset_subpixel(face, g.glyph_id, size_px, shear, sub_x)?;
if bold_r > 0.0 && !bitmap.is_empty() {
bitmap = crate::stroke::dilate_alpha(&bitmap, bold_r);
let off = crate::stroke::dilate_offset(bold_r) as f32;
off_x -= off;
off_y -= off;
}
if !bitmap.is_empty() {
let glyph_x = int_x + off_x;
let glyph_y = g.y_offset + off_y;
x_min = x_min.min(glyph_x);
y_min = y_min.min(glyph_y);
x_max = x_max.max(glyph_x + bitmap.width as f32);
y_max = y_max.max(glyph_y + bitmap.height as f32);
}
prepared.push((*g, bitmap, off_x, off_y, int_x));
pen += g.x_advance;
}
if !x_min.is_finite() {
return Ok(RgbaBitmap::default());
}
let x_origin = x_min.floor();
let y_origin = y_min.floor();
let width = (x_max.ceil() - x_origin).max(0.0) as u32;
let height = (y_max.ceil() - y_origin).max(0.0) as u32;
if width == 0 || height == 0 {
return Ok(RgbaBitmap::default());
}
let mut dst = RgbaBitmap::new(width, height);
let dw = dst.width;
let dh = dst.height;
let ds = dst.stride();
for (g, bitmap, off_x, off_y, int_x) in prepared {
if !bitmap.is_empty() {
let glyph_x = int_x + off_x - x_origin;
let glyph_y = g.y_offset + off_y - y_origin;
oxideav_pixfmt::blit_alpha_mask(
&mut dst.data,
dw,
dh,
ds,
glyph_x.round() as i32,
glyph_y.round() as i32,
&bitmap.data,
bitmap.width,
bitmap.height,
bitmap.width as usize,
color,
);
}
}
Ok(dst)
}
pub fn render_text_wrapped(
face: &Face,
text: &str,
size_px: f32,
color: Rgba,
max_width: f32,
) -> Result<Vec<RgbaBitmap>, Error> {
let lines = wrap_lines(face, text, size_px, max_width)?;
let mut out = Vec::with_capacity(lines.len());
for line in lines {
out.push(render_text(face, &line, size_px, color)?);
}
Ok(out)
}