pub mod image;
pub mod outline;
mod bitmap;
mod color;
mod hinting_cache;
mod proxy;
use hinting_cache::HintingCache;
use image::*;
use outline::*;
use skrifa::{
instance::{NormalizedCoord as SkrifaNormalizedCoord, Size as SkrifaSize},
outline::OutlineGlyphCollection,
GlyphId as SkrifaGlyphId, MetadataProvider,
};
use super::internal;
use super::{cache::FontCache, setting::Setting, FontRef, GlyphId, NormalizedCoord};
use alloc::vec::Vec;
use core::borrow::Borrow;
#[cfg(all(feature = "libm", feature = "render"))]
use core_maths::CoreFloat;
use proxy::*;
use zeno::Placement;
#[cfg(feature = "render")]
use zeno::{Format, Mask, Origin, Point, Scratch, Style, Transform, Vector};
pub(crate) use bitmap::decode_png;
pub type PaletteIndex = u16;
pub type StrikeIndex = u32;
#[derive(Copy, Clone, Debug)]
pub enum StrikeWith {
ExactSize,
BestFit,
LargestSize,
Index(StrikeIndex),
}
#[derive(Copy, Clone, Debug)]
pub enum Source {
Outline,
ColorOutline(PaletteIndex),
Bitmap(StrikeWith),
ColorBitmap(StrikeWith),
}
impl Default for Source {
fn default() -> Self {
Self::Outline
}
}
pub struct ScaleContext {
fonts: FontCache<ScalerProxy>,
state: State,
hinting_cache: HintingCache,
coords: Vec<SkrifaNormalizedCoord>,
}
struct State {
scratch0: Vec<u8>,
scratch1: Vec<u8>,
outline: Outline,
#[cfg(feature = "render")]
rcx: Scratch,
}
impl ScaleContext {
pub fn new() -> Self {
Self::with_max_entries(8)
}
pub fn with_max_entries(max_entries: usize) -> Self {
let max_entries = max_entries.clamp(1, 64);
Self {
fonts: FontCache::new(max_entries),
state: State {
scratch0: Vec::new(),
scratch1: Vec::new(),
outline: Outline::new(),
#[cfg(feature = "render")]
rcx: Scratch::new(),
},
hinting_cache: HintingCache::default(),
coords: Vec::new(),
}
}
pub fn builder<'a>(&'a mut self, font: impl Into<FontRef<'a>>) -> ScalerBuilder<'a> {
ScalerBuilder::new(self, font, None)
}
pub fn builder_with_id<'a>(
&'a mut self,
font: impl Into<FontRef<'a>>,
id: [u64; 2],
) -> ScalerBuilder<'a> {
ScalerBuilder::new(self, font, Some(id))
}
}
impl Default for ScaleContext {
fn default() -> Self {
Self::new()
}
}
pub struct ScalerBuilder<'a> {
state: &'a mut State,
hinting_cache: &'a mut HintingCache,
font: FontRef<'a>,
outlines: Option<OutlineGlyphCollection<'a>>,
proxy: &'a ScalerProxy,
id: [u64; 2],
coords: &'a mut Vec<SkrifaNormalizedCoord>,
size: f32,
hint: bool,
}
impl<'a> ScalerBuilder<'a> {
fn new(
context: &'a mut ScaleContext,
font: impl Into<FontRef<'a>>,
id: Option<[u64; 2]>,
) -> Self {
let font = font.into();
let (id, proxy) = context.fonts.get(&font, id, ScalerProxy::from_font);
let skrifa_font = if font.offset == 0 {
skrifa::FontRef::new(font.data).ok()
} else {
let index = crate::FontDataRef::new(font.data)
.and_then(|font_data| font_data.fonts().position(|f| f.offset == font.offset));
index.and_then(|index| skrifa::FontRef::from_index(font.data, index as u32).ok())
};
let outlines = skrifa_font.map(|font_ref| font_ref.outline_glyphs());
Self {
state: &mut context.state,
hinting_cache: &mut context.hinting_cache,
font,
outlines,
proxy,
id,
coords: &mut context.coords,
size: 0.,
hint: false,
}
}
pub fn size(mut self, ppem: f32) -> Self {
self.size = ppem.max(0.);
self
}
pub fn hint(mut self, yes: bool) -> Self {
self.hint = yes;
self
}
pub fn variations<I>(self, settings: I) -> Self
where
I: IntoIterator,
I::Item: Into<Setting<f32>>,
{
if self.proxy.coord_count != 0 {
let vars = self.font.variations();
self.coords.resize(vars.len(), Default::default());
for setting in settings {
let setting = setting.into();
for var in vars {
if var.tag() == setting.tag {
let value = var.normalize(setting.value);
if let Some(c) = self.coords.get_mut(var.index()) {
*c = SkrifaNormalizedCoord::from_bits(value);
}
}
}
}
}
self
}
pub fn normalized_coords<I>(self, coords: I) -> Self
where
I: IntoIterator,
I::Item: Borrow<NormalizedCoord>,
{
self.coords.clear();
self.coords.extend(
coords
.into_iter()
.map(|c| SkrifaNormalizedCoord::from_bits(*c.borrow())),
);
self
}
pub fn build(self) -> Scaler<'a> {
let upem = self.proxy.metrics.units_per_em();
let skrifa_size = if self.size != 0.0 && upem != 0 {
SkrifaSize::new(self.size)
} else {
SkrifaSize::unscaled()
};
let hinting_instance = match (self.hint, &self.outlines) {
(true, Some(outlines)) => {
let key = hinting_cache::HintingKey {
id: self.id,
outlines,
size: skrifa_size,
coords: self.coords,
};
self.hinting_cache.get(&key)
}
_ => None,
};
Scaler {
state: self.state,
font: self.font,
outlines: self.outlines,
hinting_instance,
proxy: self.proxy,
coords: &self.coords[..],
size: self.size,
skrifa_size,
}
}
}
pub struct Scaler<'a> {
state: &'a mut State,
font: FontRef<'a>,
outlines: Option<OutlineGlyphCollection<'a>>,
hinting_instance: Option<&'a skrifa::outline::HintingInstance>,
proxy: &'a ScalerProxy,
coords: &'a [SkrifaNormalizedCoord],
size: f32,
skrifa_size: SkrifaSize,
}
impl<'a> Scaler<'a> {
pub fn has_outlines(&self) -> bool {
self.outlines
.as_ref()
.map(|outlines| outlines.format().is_some())
.unwrap_or_default()
}
pub fn scale_outline_into(&mut self, glyph_id: GlyphId, outline: &mut Outline) -> bool {
outline.clear();
self.scale_outline_impl(glyph_id, None, Some(outline))
}
pub fn scale_outline(&mut self, glyph_id: GlyphId) -> Option<Outline> {
let mut outline = Outline::new();
if self.scale_outline_into(glyph_id, &mut outline) {
Some(outline)
} else {
None
}
}
pub fn has_color_outlines(&self) -> bool {
self.proxy.color.colr != 0 && self.proxy.color.cpal != 0
}
pub fn scale_color_outline_into(&mut self, glyph_id: GlyphId, outline: &mut Outline) -> bool {
outline.clear();
if !self.has_color_outlines() {
return false;
}
let layers = match self.proxy.color.layers(self.font.data, glyph_id) {
Some(layers) => layers,
_ => return false,
};
for i in 0..layers.len() {
let layer = match layers.get(i) {
Some(layer) => layer,
_ => return false,
};
if !self.scale_outline_impl(layer.glyph_id, layer.color_index, Some(outline)) {
return false;
}
}
outline.set_color(true);
true
}
pub fn scale_color_outline(&mut self, glyph_id: GlyphId) -> Option<Outline> {
let mut outline = Outline::new();
if self.scale_color_outline_into(glyph_id, &mut outline) {
Some(outline)
} else {
None
}
}
fn scale_outline_impl(
&mut self,
glyph_id: GlyphId,
color_index: Option<u16>,
outline: Option<&mut Outline>,
) -> bool {
let mut outline = match outline {
Some(x) => x,
_ => &mut self.state.outline,
};
if let Some(outlines) = &self.outlines {
if let Some(glyph) = outlines.get(SkrifaGlyphId::from(glyph_id)) {
outline.begin_layer(color_index);
let settings: skrifa::outline::DrawSettings =
if let Some(hinting_instance) = &self.hinting_instance {
(*hinting_instance).into()
} else {
(
self.skrifa_size,
skrifa::instance::LocationRef::new(self.coords),
)
.into()
};
if glyph
.draw(settings, &mut OutlineWriter(&mut outline))
.is_ok()
{
outline.maybe_close();
outline.finish();
return true;
}
}
}
false
}
#[allow(dead_code)]
fn scale_color_outline_impl(&mut self, glyph_id: GlyphId) -> bool {
if !self.has_color_outlines() {
return false;
}
let layers = match self.proxy.color.layers(self.font.data, glyph_id) {
Some(layers) => layers,
_ => return false,
};
self.state.outline.clear();
for i in 0..layers.len() {
let layer = match layers.get(i) {
Some(layer) => layer,
_ => return false,
};
if !self.scale_outline_impl(layer.glyph_id, layer.color_index, None) {
return false;
}
}
true
}
pub fn has_bitmaps(&self) -> bool {
self.proxy.bitmaps.has_alpha()
}
pub fn scale_bitmap_into(
&mut self,
glyph_id: u16,
strike: StrikeWith,
image: &mut Image,
) -> bool {
self.scale_bitmap_impl(glyph_id, false, strike, image) == Some(true)
}
pub fn scale_bitmap(&mut self, glyph_id: u16, strike: StrikeWith) -> Option<Image> {
let mut image = Image::new();
if self.scale_bitmap_into(glyph_id, strike, &mut image) {
Some(image)
} else {
None
}
}
pub fn has_color_bitmaps(&self) -> bool {
self.proxy.bitmaps.has_color()
}
pub fn scale_color_bitmap_into(
&mut self,
glyph_id: u16,
strike: StrikeWith,
image: &mut Image,
) -> bool {
self.scale_bitmap_impl(glyph_id, true, strike, image) == Some(true)
}
pub fn scale_color_bitmap(&mut self, glyph_id: u16, strike: StrikeWith) -> Option<Image> {
let mut image = Image::new();
if self.scale_color_bitmap_into(glyph_id, strike, &mut image) {
Some(image)
} else {
None
}
}
fn scale_bitmap_impl(
&mut self,
glyph_id: GlyphId,
color: bool,
strike: StrikeWith,
image: &mut Image,
) -> Option<bool> {
image.clear();
let size = self.size;
let mut strikes = if color {
self.proxy.bitmaps.materialize_color(&self.font)
} else {
self.proxy.bitmaps.materialize_alpha(&self.font)
};
let bitmap = match strike {
StrikeWith::ExactSize => {
if self.size == 0. {
None
} else {
strikes
.find_by_exact_ppem(size as u16, glyph_id)?
.get(glyph_id)
}
}
StrikeWith::BestFit => {
if self.size == 0. {
None
} else {
strikes
.find_by_nearest_ppem(size as u16, glyph_id)?
.get(glyph_id)
}
}
StrikeWith::LargestSize => strikes.find_by_largest_ppem(glyph_id)?.get(glyph_id),
StrikeWith::Index(i) => strikes
.nth(i as usize)
.and_then(|strike| strike.get(glyph_id)),
}?;
if bitmap.ppem == 0 {
return None;
}
let (_, _, bufsize) = bitmap.scaled_size(size);
image.data.resize(bufsize, 0);
self.state.scratch0.clear();
self.state.scratch1.clear();
let mut w = bitmap.width;
let mut h = bitmap.height;
let scale = size / bitmap.ppem as f32;
image.placement = if size != 0. && scale != 1. {
self.state
.scratch0
.resize(bitmap.format.buffer_size(w, h), 0);
w = (w as f32 * scale) as u32;
h = (h as f32 * scale) as u32;
image.data.resize(bitmap.format.buffer_size(w, h), 0);
if !bitmap.decode(Some(&mut self.state.scratch1), &mut self.state.scratch0) {
return None;
}
if !bitmap::resize(
&self.state.scratch0,
bitmap.width,
bitmap.height,
bitmap.format.channels(),
&mut image.data,
w,
h,
bitmap::Filter::Mitchell,
Some(&mut self.state.scratch1),
) {
return None;
}
let left = (bitmap.left as f32 * scale) as i32;
let top = (bitmap.top as f32 * scale) as i32;
Placement {
left,
top,
width: w,
height: h,
}
} else {
image.data.resize(bitmap.format.buffer_size(w, h), 0);
if !bitmap.decode(Some(&mut self.state.scratch1), &mut image.data) {
return None;
}
Placement {
left: bitmap.left,
top: bitmap.top,
width: w,
height: h,
}
};
image.source = match color {
true => Source::ColorBitmap(strike),
false => Source::Bitmap(strike),
};
image.content = match bitmap.format.channels() {
1 => Content::Mask,
_ => Content::Color,
};
Some(true)
}
}
#[cfg(feature = "render")]
pub struct Render<'a> {
sources: &'a [Source],
format: Format,
offset: Point,
transform: Option<Transform>,
embolden: f32,
foreground: [u8; 4],
style: Style<'a>,
}
#[cfg(feature = "render")]
impl<'a> Render<'a> {
pub fn new(sources: &'a [Source]) -> Self {
Self {
sources,
format: Format::Alpha,
offset: Point::new(0., 0.),
transform: None,
embolden: 0.,
foreground: [128, 128, 128, 255],
style: Style::default(),
}
}
pub fn format(&mut self, format: Format) -> &mut Self {
self.format = format;
self
}
pub fn style(&mut self, style: impl Into<Style<'a>>) -> &mut Self {
self.style = style.into();
self
}
pub fn offset(&mut self, offset: Vector) -> &mut Self {
self.offset = offset;
self
}
pub fn transform(&mut self, transform: Option<Transform>) -> &mut Self {
self.transform = transform;
self
}
pub fn embolden(&mut self, strength: f32) -> &mut Self {
self.embolden = strength;
self
}
pub fn default_color(&mut self, color: [u8; 4]) -> &mut Self {
self.foreground = color;
self
}
pub fn render_into(&self, scaler: &mut Scaler, glyph_id: GlyphId, image: &mut Image) -> bool {
for source in self.sources {
match source {
Source::Outline => {
if !scaler.has_outlines() {
continue;
}
scaler.state.outline.clear();
if scaler.scale_outline_impl(glyph_id, None, None) {
let state = &mut scaler.state;
let rcx = &mut state.rcx;
let outline = &mut state.outline;
if self.embolden != 0. {
outline.embolden(self.embolden, self.embolden);
}
if let Some(transform) = &self.transform {
outline.transform(transform);
}
let placement = Mask::with_scratch(outline.path(), rcx)
.format(self.format)
.origin(Origin::BottomLeft)
.style(self.style)
.offset(self.offset)
.render_offset(self.offset)
.inspect(|fmt, w, h| {
image.data.resize(fmt.buffer_size(w, h), 0);
})
.render_into(&mut image.data[..], None);
image.placement = placement;
image.content = if self.format == Format::Alpha {
Content::Mask
} else {
Content::SubpixelMask
};
image.source = Source::Outline;
return true;
}
}
Source::ColorOutline(palette_index) => {
if !scaler.has_color_outlines() {
continue;
}
scaler.state.outline.clear();
if scaler.scale_color_outline_impl(glyph_id) {
let font = &scaler.font;
let proxy = &scaler.proxy;
let state = &mut scaler.state;
let scratch = &mut state.scratch0;
let rcx = &mut state.rcx;
let outline = &mut state.outline;
if let Some(transform) = &self.transform {
outline.transform(transform);
}
let palette = proxy.color.palette(font, *palette_index);
let total_bounds = outline.bounds();
let base_x = (total_bounds.min.x + self.offset.x).floor() as i32;
let base_y = (total_bounds.min.y + self.offset.y).ceil() as i32;
let base_w = total_bounds.width().ceil() as u32;
let base_h = total_bounds.height().ceil() as u32;
image.data.resize((base_w * base_h * 4) as usize, 0);
image.placement.left = base_x;
image.placement.top = base_h as i32 + base_y;
image.placement.width = total_bounds.width().ceil() as u32;
image.placement.height = total_bounds.height().ceil() as u32;
let mut ok = true;
for i in 0..outline.len() {
let layer = match outline.get(i) {
Some(layer) => layer,
_ => {
ok = false;
break;
}
};
scratch.clear();
let placement = Mask::with_scratch(layer.path(), rcx)
.origin(Origin::BottomLeft)
.style(self.style)
.offset(self.offset)
.render_offset(self.offset)
.inspect(|fmt, w, h| {
scratch.resize(fmt.buffer_size(w, h), 0);
})
.render_into(&mut scratch[..], None);
let color = layer
.color_index()
.and_then(|i| palette.map(|p| p.get(i)))
.unwrap_or(self.foreground);
bitmap::blit(
&scratch[..],
placement.width,
placement.height,
placement.left.wrapping_sub(base_x),
(base_h as i32 + base_y).wrapping_sub(placement.top),
color,
&mut image.data,
base_w,
base_h,
);
}
if ok {
image.source = Source::ColorOutline(*palette_index);
image.content = Content::Color;
return true;
}
}
}
Source::Bitmap(mode) => {
if !scaler.has_bitmaps() {
continue;
}
if scaler.scale_bitmap_into(glyph_id, *mode, image) {
return true;
}
}
Source::ColorBitmap(mode) => {
if !scaler.has_color_bitmaps() {
continue;
}
if scaler.scale_color_bitmap_into(glyph_id, *mode, image) {
return true;
}
}
}
}
false
}
pub fn render(&self, scaler: &mut Scaler, glyph_id: GlyphId) -> Option<Image> {
let mut image = Image::new();
if self.render_into(scaler, glyph_id, &mut image) {
Some(image)
} else {
None
}
}
}