#![allow(
clippy::cognitive_complexity,
clippy::doc_markdown,
clippy::cast_lossless,
clippy::many_single_char_names
)]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
mod font;
mod geometry;
mod outliner;
#[cfg(all(feature = "libm-math", not(feature = "std")))]
mod nostd_float;
#[cfg(feature = "gpu_cache")]
pub mod gpu_cache;
pub use crate::geometry::{point, vector, Point, Rect, Vector};
pub use font::*;
use core::fmt;
#[cfg(all(feature = "libm-math", not(feature = "std")))]
use crate::nostd_float::FloatExt;
pub use owned_ttf_parser::OutlineBuilder;
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct GlyphId(pub u16);
impl From<owned_ttf_parser::GlyphId> for GlyphId {
fn from(id: owned_ttf_parser::GlyphId) -> Self {
Self(id.0)
}
}
impl From<GlyphId> for owned_ttf_parser::GlyphId {
fn from(id: GlyphId) -> Self {
Self(id.0)
}
}
#[derive(Clone)]
pub struct Glyph<'font> {
font: Font<'font>,
id: GlyphId,
}
impl<'font> Glyph<'font> {
pub fn font(&self) -> &Font<'font> {
&self.font
}
pub fn id(&self) -> GlyphId {
self.id
}
pub fn scaled(self, scale: Scale) -> ScaledGlyph<'font> {
let scale_y = self.font.scale_for_pixel_height(scale.y);
let scale_x = scale_y * scale.x / scale.y;
ScaledGlyph {
g: self,
api_scale: scale,
scale: vector(scale_x, scale_y),
}
}
}
impl fmt::Debug for Glyph<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Glyph").field("id", &self.id().0).finish()
}
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct HMetrics {
pub advance_width: f32,
pub left_side_bearing: f32,
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct VMetrics {
pub ascent: f32,
pub descent: f32,
pub line_gap: f32,
}
impl core::ops::Mul<f32> for VMetrics {
type Output = VMetrics;
fn mul(self, rhs: f32) -> Self {
Self {
ascent: self.ascent * rhs,
descent: self.descent * rhs,
line_gap: self.line_gap * rhs,
}
}
}
#[derive(Clone)]
pub struct ScaledGlyph<'font> {
g: Glyph<'font>,
api_scale: Scale,
scale: Vector<f32>,
}
impl<'font> ScaledGlyph<'font> {
pub fn id(&self) -> GlyphId {
self.g.id()
}
#[inline]
pub fn font(&self) -> &Font<'font> {
self.g.font()
}
pub fn into_unscaled(self) -> Glyph<'font> {
self.g
}
pub fn unscaled(&self) -> &Glyph<'font> {
&self.g
}
pub fn build_outline(&self, builder: &mut impl OutlineBuilder) -> bool {
let mut outliner =
crate::outliner::OutlineScaler::new(builder, vector(self.scale.x, -self.scale.y));
self.font()
.inner()
.outline_glyph(self.id().into(), &mut outliner)
.is_some()
}
pub fn positioned(self, p: Point<f32>) -> PositionedGlyph<'font> {
let bb = self.pixel_bounds_at(p);
PositionedGlyph {
sg: self,
position: p,
bb,
}
}
pub fn scale(&self) -> Scale {
self.api_scale
}
pub fn h_metrics(&self) -> HMetrics {
let inner = self.font().inner();
let id = self.id().into();
let advance = inner.glyph_hor_advance(id).unwrap();
let left_side_bearing = inner.glyph_hor_side_bearing(id).unwrap();
HMetrics {
advance_width: advance as f32 * self.scale.x,
left_side_bearing: left_side_bearing as f32 * self.scale.x,
}
}
pub fn exact_bounding_box(&self) -> Option<Rect<f32>> {
let owned_ttf_parser::Rect {
x_min,
y_min,
x_max,
y_max,
} = self.font().inner().glyph_bounding_box(self.id().into())?;
Some(Rect {
min: point(x_min as f32 * self.scale.x, -y_max as f32 * self.scale.y),
max: point(x_max as f32 * self.scale.x, -y_min as f32 * self.scale.y),
})
}
fn glyph_bitmap_box_subpixel(
&self,
font: &Font<'font>,
shift_x: f32,
shift_y: f32,
) -> Option<Rect<i32>> {
let owned_ttf_parser::Rect {
x_min,
y_min,
x_max,
y_max,
} = font.inner().glyph_bounding_box(self.id().into())?;
Some(Rect {
min: point(
(x_min as f32 * self.scale.x + shift_x).floor() as i32,
(-y_max as f32 * self.scale.y + shift_y).floor() as i32,
),
max: point(
(x_max as f32 * self.scale.x + shift_x).ceil() as i32,
(-y_min as f32 * self.scale.y + shift_y).ceil() as i32,
),
})
}
#[inline]
fn pixel_bounds_at(&self, p: Point<f32>) -> Option<Rect<i32>> {
let (x_trunc, x_fract) = (p.x.trunc() as i32, p.x.fract());
let (y_trunc, y_fract) = (p.y.trunc() as i32, p.y.fract());
let Rect { min, max } = self.glyph_bitmap_box_subpixel(self.font(), x_fract, y_fract)?;
Some(Rect {
min: point(x_trunc + min.x, y_trunc + min.y),
max: point(x_trunc + max.x, y_trunc + max.y),
})
}
}
impl fmt::Debug for ScaledGlyph<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ScaledGlyph")
.field("id", &self.id().0)
.field("scale", &self.api_scale)
.finish()
}
}
#[derive(Clone)]
pub struct PositionedGlyph<'font> {
sg: ScaledGlyph<'font>,
position: Point<f32>,
bb: Option<Rect<i32>>,
}
impl<'font> PositionedGlyph<'font> {
pub fn id(&self) -> GlyphId {
self.sg.id()
}
#[inline]
pub fn font(&self) -> &Font<'font> {
self.sg.font()
}
pub fn unpositioned(&self) -> &ScaledGlyph<'font> {
&self.sg
}
pub fn into_unpositioned(self) -> ScaledGlyph<'font> {
self.sg
}
pub fn pixel_bounding_box(&self) -> Option<Rect<i32>> {
self.bb
}
pub fn scale(&self) -> Scale {
self.sg.api_scale
}
pub fn position(&self) -> Point<f32> {
self.position
}
pub fn build_outline(&self, builder: &mut impl OutlineBuilder) -> bool {
let bb = if let Some(bb) = self.bb.as_ref() {
bb
} else {
return false;
};
let offset = vector(bb.min.x as f32, bb.min.y as f32);
let mut outliner = crate::outliner::OutlineTranslator::new(builder, self.position - offset);
self.sg.build_outline(&mut outliner)
}
pub fn draw<O: FnMut(u32, u32, f32)>(&self, o: O) {
let bb = if let Some(bb) = self.bb.as_ref() {
bb
} else {
return;
};
let width = (bb.max.x - bb.min.x) as u32;
let height = (bb.max.y - bb.min.y) as u32;
let mut outliner = crate::outliner::OutlineRasterizer::new(width as _, height as _);
self.build_outline(&mut outliner);
outliner.rasterizer.for_each_pixel_2d(o);
}
pub fn set_position(&mut self, p: Point<f32>) {
let p_diff = p - self.position;
if p_diff.x.fract().is_near_zero() && p_diff.y.fract().is_near_zero() {
if let Some(bb) = self.bb.as_mut() {
let rounded_diff = vector(p_diff.x.round() as i32, p_diff.y.round() as i32);
bb.min = bb.min + rounded_diff;
bb.max = bb.max + rounded_diff;
}
} else {
self.bb = self.sg.pixel_bounds_at(p);
}
self.position = p;
}
}
impl fmt::Debug for PositionedGlyph<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PositionedGlyph")
.field("id", &self.id().0)
.field("scale", &self.scale())
.field("position", &self.position)
.finish()
}
}
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
pub struct Scale {
pub x: f32,
pub y: f32,
}
impl Scale {
#[inline]
pub fn uniform(s: f32) -> Scale {
Scale { x: s, y: s }
}
}
pub trait IntoGlyphId {
fn into_glyph_id(self, font: &Font<'_>) -> GlyphId;
}
impl IntoGlyphId for char {
#[inline]
fn into_glyph_id(self, font: &Font<'_>) -> GlyphId {
font.inner()
.glyph_index(self)
.unwrap_or(owned_ttf_parser::GlyphId(0))
.into()
}
}
impl<G: Into<GlyphId>> IntoGlyphId for G {
#[inline]
fn into_glyph_id(self, _font: &Font<'_>) -> GlyphId {
self.into()
}
}
#[derive(Clone)]
pub struct GlyphIter<'a, 'font, I: Iterator>
where
I::Item: IntoGlyphId,
{
font: &'a Font<'font>,
itr: I,
}
impl<'a, 'font, I> Iterator for GlyphIter<'a, 'font, I>
where
I: Iterator,
I::Item: IntoGlyphId,
{
type Item = Glyph<'font>;
fn next(&mut self) -> Option<Glyph<'font>> {
self.itr.next().map(|c| self.font.glyph(c))
}
}
#[derive(Clone)]
pub struct LayoutIter<'a, 'font, 's> {
font: &'a Font<'font>,
chars: core::str::Chars<'s>,
caret: f32,
scale: Scale,
start: Point<f32>,
last_glyph: Option<GlyphId>,
}
impl<'a, 'font, 's> Iterator for LayoutIter<'a, 'font, 's> {
type Item = PositionedGlyph<'font>;
fn next(&mut self) -> Option<PositionedGlyph<'font>> {
self.chars.next().map(|c| {
let g = self.font.glyph(c).scaled(self.scale);
if let Some(last) = self.last_glyph {
self.caret += self.font.pair_kerning(self.scale, last, g.id());
}
let g = g.positioned(point(self.start.x + self.caret, self.start.y));
self.caret += g.sg.h_metrics().advance_width;
self.last_glyph = Some(g.id());
g
})
}
}
pub(crate) trait NearZero {
fn is_near_zero(&self) -> bool;
}
impl NearZero for f32 {
#[inline]
fn is_near_zero(&self) -> bool {
self.abs() <= core::f32::EPSILON
}
}