use alloc::rc::Rc;
use super::{Font, FontBackend, FontMetrics, FontProvider, Glyph, GlyphKind};
use mirx::font::FontGlyphs;
use mirx::{
FontError, FontRepresentationFallback, FontRepresentationRequest, FontView, PayloadLimits,
image::{CoverageBudget, SurfaceRequirements, SurfaceView, UnitGroup},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum MirxFontError {
Container,
MissingFace,
MultipleFaces,
InvalidFace(FontError),
EncodedStorage,
SurfaceSlotsTooSmall {
needed: usize,
available: usize,
},
SurfaceOutputTooSmall {
surface: usize,
needed: usize,
available: usize,
},
SurfaceSizeOverflow {
surface: usize,
},
SurfaceWorkspace {
surface: usize,
error: mirx::image::BufferRequirementError,
},
EncodedSurface {
surface: usize,
error: mirx::font::EncodedGlyphError,
},
}
pub struct MirxFontStorage<'scratch> {
surfaces: &'static mut [Option<SurfaceView<'static>>],
output: &'static mut [u8],
groups: &'scratch mut [Option<UnitGroup<'static>>],
workspace: &'scratch mut [u8],
requirements: SurfaceRequirements,
}
impl<'scratch> MirxFontStorage<'scratch> {
pub fn new(
surfaces: &'static mut [Option<SurfaceView<'static>>],
output: &'static mut [u8],
groups: &'scratch mut [Option<UnitGroup<'static>>],
workspace: &'scratch mut [u8],
) -> Self {
Self {
surfaces,
output,
groups,
workspace,
requirements: SurfaceRequirements::new(),
}
}
pub const fn with_requirements(mut self, requirements: SurfaceRequirements) -> Self {
self.requirements = requirements;
self
}
}
#[derive(Clone, Copy, Debug)]
pub struct MirxFontProvider {
face: FontView<'static>,
default_size: u16,
decoded: &'static [Option<SurfaceView<'static>>],
}
impl MirxFontProvider {
pub fn from_mirx(bytes: &'static [u8], limits: &PayloadLimits) -> Result<Self, MirxFontError> {
let reader = mirx::Reader::open(bytes).map_err(|_| MirxFontError::Container)?;
let mut faces = reader
.chunks()
.filter(|chunk| chunk.chunk_type() == mirx::ChunkType::FONT);
let chunk = faces.next().ok_or(MirxFontError::MissingFace)?;
if faces.next().is_some() {
return Err(MirxFontError::MultipleFaces);
}
let face = chunk
.font(limits)
.map_err(MirxFontError::InvalidFace)?
.expect("FONT filter");
face.preflight(limits).map_err(MirxFontError::InvalidFace)?;
Self::from_raw_view(face)
}
pub fn from_payload(
payload: &'static [u8],
limits: &PayloadLimits,
) -> Result<Self, MirxFontError> {
let face = FontView::open(payload, limits).map_err(MirxFontError::InvalidFace)?;
face.preflight(limits).map_err(MirxFontError::InvalidFace)?;
Self::from_raw_view(face)
}
pub fn from_mirx_with_storage(
bytes: &'static [u8],
limits: &PayloadLimits,
storage: MirxFontStorage<'_>,
) -> Result<Self, MirxFontError> {
let reader = mirx::Reader::open(bytes).map_err(|_| MirxFontError::Container)?;
let mut faces = reader
.chunks()
.filter(|chunk| chunk.chunk_type() == mirx::ChunkType::FONT);
let chunk = faces.next().ok_or(MirxFontError::MissingFace)?;
if faces.next().is_some() {
return Err(MirxFontError::MultipleFaces);
}
let face = chunk
.font(limits)
.map_err(MirxFontError::InvalidFace)?
.expect("FONT filter");
face.preflight(limits).map_err(MirxFontError::InvalidFace)?;
Self::from_view_with_storage(face, limits, storage)
}
pub fn from_payload_with_storage(
payload: &'static [u8],
limits: &PayloadLimits,
storage: MirxFontStorage<'_>,
) -> Result<Self, MirxFontError> {
let face = FontView::open(payload, limits).map_err(MirxFontError::InvalidFace)?;
face.preflight(limits).map_err(MirxFontError::InvalidFace)?;
Self::from_view_with_storage(face, limits, storage)
}
fn from_raw_view(face: FontView<'static>) -> Result<Self, MirxFontError> {
for index in 0..face.tables().len() {
if matches!(face.glyphs(index), Some(FontGlyphs::Encoded(_))) {
return Err(MirxFontError::EncodedStorage);
}
}
Ok(Self::from_view(face, &[]))
}
fn from_view_with_storage(
face: FontView<'static>,
limits: &PayloadLimits,
storage: MirxFontStorage<'_>,
) -> Result<Self, MirxFontError> {
let MirxFontStorage {
surfaces,
output,
groups,
workspace,
requirements,
} = storage;
if surfaces.len() < face.surface_count() {
return Err(MirxFontError::SurfaceSlotsTooSmall {
needed: face.surface_count(),
available: surfaces.len(),
});
}
let mut output_cursor = 0usize;
let mut budget = CoverageBudget::new(limits.max_raster_work());
for surface in 0..face.surface_count() {
let Some(FontGlyphs::Encoded(encoded)) = Self::surface_storage(face, surface) else {
continue;
};
let prepared = encoded
.groups_into(groups, &mut budget)
.map_err(|error| MirxFontError::EncodedSurface { surface, error })?;
let plan = prepared
.decode_surface_plan(requirements, limits)
.map_err(|error| MirxFontError::EncodedSurface { surface, error })?;
let needed = plan.memory_plan().buffer_requirements();
let address = (output.as_ptr() as usize)
.checked_add(output_cursor)
.ok_or(MirxFontError::SurfaceSizeOverflow { surface })?;
let alignment = needed.base_alignment();
let alignment = usize::try_from(alignment.get()).expect("u32 fits usize");
let padding = (alignment - address % alignment) % alignment;
let span = padding
.checked_add(needed.byte_len())
.ok_or(MirxFontError::SurfaceSizeOverflow { surface })?;
let available = output.len().saturating_sub(output_cursor);
if span > available {
return Err(MirxFontError::SurfaceOutputTooSmall {
surface,
needed: span,
available,
});
}
needed
.validate(&output[output_cursor + padding..])
.expect("checked aligned surface range");
plan.workspace_requirements()
.validate(workspace)
.map_err(|error| MirxFontError::SurfaceWorkspace { surface, error })?;
output_cursor += span;
}
surfaces[..face.surface_count()].fill(None);
let mut remaining = output;
let mut budget = CoverageBudget::new(limits.max_raster_work());
for (surface, slot) in surfaces.iter_mut().enumerate().take(face.surface_count()) {
let Some(FontGlyphs::Encoded(encoded)) = Self::surface_storage(face, surface) else {
continue;
};
let prepared = encoded
.groups_into(groups, &mut budget)
.map_err(|error| MirxFontError::EncodedSurface { surface, error })?;
let plan = prepared
.decode_surface_plan(requirements, limits)
.map_err(|error| MirxFontError::EncodedSurface { surface, error })?;
let needed = plan.memory_plan().buffer_requirements();
let address = remaining.as_ptr() as usize;
let alignment = needed.base_alignment();
let alignment = usize::try_from(alignment.get()).expect("u32 fits usize");
let padding = (alignment - address % alignment) % alignment;
let span = padding
.checked_add(needed.byte_len())
.expect("surface span admitted in the first pass");
let current = core::mem::take(&mut remaining);
let (allocation, rest) = current.split_at_mut(span);
remaining = rest;
let decoded = plan
.decode_into(&mut allocation[padding..], workspace)
.expect("immutable font surface admitted in the first pass");
*slot = Some(decoded);
}
Ok(Self::from_view(face, &surfaces[..face.surface_count()]))
}
fn surface_storage(face: FontView<'static>, surface: usize) -> Option<FontGlyphs<'static>> {
let representation = (0..face.tables().len()).find(|index| {
face.tables()
.get(*index)
.is_some_and(|value| usize::from(value.record().surface_index()) == surface)
})?;
face.glyphs(representation)
}
fn from_view(
face: FontView<'static>,
decoded: &'static [Option<SurfaceView<'static>>],
) -> Self {
let representations = face.tables().representations();
let default_size = representations
.iter()
.map(|record| record.representation())
.filter(|representation| {
matches!(
representation.kind(),
mirx::FontRepresentationKind::Coverage { .. }
)
})
.map(|representation| representation.design_ppem())
.max()
.unwrap_or_else(|| {
representations
.get(0)
.expect("nonempty admitted face")
.representation()
.design_ppem()
});
Self {
face,
default_size,
decoded,
}
}
pub const fn default_size(self) -> u16 {
self.default_size
}
pub const fn view(self) -> FontView<'static> {
self.face
}
fn selected(&self, size: u16) -> Option<mirx::font::FaceRepresentation<'static>> {
self.face
.tables()
.select(
FontRepresentationRequest::new(size)
.with_fallback(FontRepresentationFallback::Nearest),
)
.ok()
}
}
impl FontProvider for MirxFontProvider {
fn glyph(&self, ch: char, requested_size: u16) -> Option<Glyph> {
let selected = self.selected(requested_size)?;
let ordinal = self.face.tables().codepoints().binary_search(ch).ok()?;
let metric = selected.metrics().get(ordinal)?;
let (plane, region) = match self.face.glyphs(selected.index())? {
FontGlyphs::Raw(storage) => {
let raster = storage.get(ordinal)?;
(raster.storage().plane(0)?, raster.region())
}
FontGlyphs::Encoded(_) => {
let surface = usize::from(selected.record().surface_index());
let decoded = self.decoded.get(surface).copied().flatten()?;
(decoded.plane(0)?, selected.map().get(ordinal)?)
}
};
Some(Glyph {
advance: metric.advance().into(),
kind: GlyphKind::Raster {
samples: plane.bytes(),
stride: plane.memory().stride(),
region,
representation: selected.record().representation(),
bearing_x: metric.bearing_x().into(),
bearing_y: metric.bearing_y().into(),
},
})
}
fn metrics(&self, requested_size: u16) -> FontMetrics {
self.selected(requested_size)
.map(|selected| {
let metrics = selected.metrics().line_metrics();
let scale = crate::types::Fixed::from_int(i32::from(requested_size))
/ crate::types::Fixed::from_int(i32::from(
selected.record().representation().design_ppem(),
));
FontMetrics {
ascender: crate::types::Fixed::from(metrics.ascent()) * scale,
descender: crate::types::Fixed::from(metrics.descent()) * scale,
line_height: crate::types::Fixed::from(metrics.line_height()) * scale,
}
})
.unwrap_or(FontMetrics {
ascender: crate::types::Fixed::ZERO,
descender: crate::types::Fixed::ZERO,
line_height: crate::types::Fixed::ONE,
})
}
}
pub fn font_from_mirx(
family: &'static str,
bytes: &'static [u8],
limits: &PayloadLimits,
) -> Result<Font, MirxFontError> {
let provider = MirxFontProvider::from_mirx(bytes, limits)?;
Ok(Font {
family,
size: provider.default_size(),
backend: FontBackend::Custom(Rc::new(provider)),
})
}
pub fn font_from_mirx_with_storage(
family: &'static str,
bytes: &'static [u8],
limits: &PayloadLimits,
storage: MirxFontStorage<'_>,
) -> Result<Font, MirxFontError> {
let provider = MirxFontProvider::from_mirx_with_storage(bytes, limits, storage)?;
Ok(Font {
family,
size: provider.default_size(),
backend: FontBackend::Custom(Rc::new(provider)),
})
}
#[cfg(test)]
mod tests {
use super::*;
use mirx::{
Fixed,
coding::Rle,
font::{
FontAsset, GlyphMap, GlyphMetrics, GlyphSurfaceAsset, LineMetrics, RawGlyphs,
RepresentationAsset,
},
image::{
ColorDescription, EncodedImageAsset, PlaneMemoryLayout, Region, SampleLayout,
SurfaceDescriptor,
},
};
fn atlas_face() -> MirxFontProvider {
let codepoints = [' ', 'A'];
let regions = [
Region::new(0, 0, 0, 0).unwrap(),
Region::new(3, 1, 3, 2).unwrap(),
];
let map = GlyphMap::atlas(8, 4, ®ions).unwrap();
let surface =
SurfaceDescriptor::new(8, 4, SampleLayout::A1, ColorDescription::NONE).unwrap();
let memory = PlaneMemoryLayout::builder(surface.plane(0).unwrap())
.with_stride(64)
.with_alignment(mirx::ByteAlignment::new(64).unwrap())
.build()
.unwrap();
let samples = [0xa5; 256];
let glyphs = RawGlyphs::builder(map, SampleLayout::A1)
.with_memory_layout(memory)
.build(&samples)
.unwrap();
let metrics = [
GlyphMetrics::new(Fixed::from_int(4), Fixed::ZERO, Fixed::from_int(12)),
GlyphMetrics::new(
Fixed::from_ratio(11, 2),
Fixed::from_ratio(-1, 2),
Fixed::from_ratio(45, 4),
),
];
let line = LineMetrics::new(
Fixed::from_int(12),
Fixed::from_int(-4),
Fixed::from_int(16),
)
.unwrap();
let representation = RepresentationAsset::new(
mirx::FontRepresentation::coverage(1, 16, 4).unwrap(),
0,
line,
&metrics,
)
.with_map(0);
let payload = FontAsset::new(
&codepoints,
&[representation],
&[GlyphSurfaceAsset::raw(glyphs)],
)
.with_maps(&[map])
.encode()
.unwrap();
MirxFontProvider::from_payload(alloc::vec::Vec::leak(payload), &PayloadLimits::HOST)
.unwrap()
}
#[test]
fn atlas_regions_and_fractional_metrics_reach_the_renderer_unchanged() {
let provider = atlas_face();
let glyph = provider.glyph('A', 16).unwrap();
assert_eq!(glyph.advance, crate::types::Fixed::from_ratio(11, 2));
let GlyphKind::Raster {
stride,
region,
bearing_x,
bearing_y,
..
} = glyph.kind
else {
panic!("raster glyph");
};
assert_eq!(stride, 64);
assert_eq!(
(region.x(), region.y(), region.width(), region.height()),
(3, 1, 3, 2)
);
assert_eq!(bearing_x, crate::types::Fixed::from_ratio(-1, 2));
assert_eq!(bearing_y, crate::types::Fixed::from_ratio(45, 4));
assert!(matches!(
provider.glyph(' ', 16).unwrap().kind,
GlyphKind::Raster { region, .. } if region.is_empty()
));
assert!(provider.glyph('Z', 16).is_none());
}
#[test]
fn size_specific_line_and_glyph_metrics_use_one_representation() {
let provider = atlas_face();
let metrics = provider.metrics(8);
assert_eq!(metrics.ascender, crate::types::Fixed::from_int(6));
assert_eq!(metrics.descender, crate::types::Fixed::from_int(-2));
assert_eq!(metrics.line_height, crate::types::Fixed::from_int(8));
assert_eq!(
provider.glyph('A', 8).unwrap().advance,
crate::types::Fixed::from_ratio(11, 2)
);
}
#[test]
fn encoded_surfaces_require_an_explicit_decode_workspace_provider() {
let codepoints = ['A', 'B'];
let metrics = [GlyphMetrics::default(); 2];
let line = LineMetrics::new(Fixed::ZERO, Fixed::ZERO, Fixed::ONE).unwrap();
let map = GlyphMap::glyph_major(2, 2, codepoints.len()).unwrap();
let surface =
SurfaceDescriptor::new(2, 4, SampleLayout::A8, ColorDescription::NONE).unwrap();
let image = EncodedImageAsset::new(surface, Rle::new().record(), &[0x87, 42]);
let representation = RepresentationAsset::new(
mirx::FontRepresentation::coverage(8, 1, 8).unwrap(),
0,
line,
&metrics,
);
let payload = FontAsset::new(
&codepoints,
&[representation],
&[GlyphSurfaceAsset::Encoded { map, image }],
)
.encode()
.unwrap();
assert!(matches!(
MirxFontProvider::from_payload(alloc::vec::Vec::leak(payload), &PayloadLimits::HOST),
Err(MirxFontError::EncodedStorage)
));
}
fn encoded_face_payload() -> &'static [u8] {
let codepoints = ['A', 'B'];
let metrics = [GlyphMetrics::default(); 2];
let line = LineMetrics::new(Fixed::ZERO, Fixed::ZERO, Fixed::ONE).unwrap();
let map = GlyphMap::glyph_major(2, 2, codepoints.len()).unwrap();
let surface =
SurfaceDescriptor::new(2, 4, SampleLayout::A8, ColorDescription::NONE).unwrap();
let image = EncodedImageAsset::new(surface, Rle::new().record(), &[0x87, 42]);
let representation = RepresentationAsset::new(
mirx::FontRepresentation::coverage(8, 1, 8).unwrap(),
0,
line,
&metrics,
);
alloc::vec::Vec::leak(
FontAsset::new(
&codepoints,
&[representation],
&[GlyphSurfaceAsset::Encoded { map, image }],
)
.encode()
.unwrap(),
)
}
#[test]
fn encoded_surfaces_reside_in_aligned_caller_storage() {
#[repr(align(64))]
struct Aligned([u8; 320]);
let payload = encoded_face_payload();
let surfaces = alloc::boxed::Box::leak(alloc::boxed::Box::new([None]));
let output =
&mut alloc::boxed::Box::leak(alloc::boxed::Box::new(Aligned([0xa5; 320]))).0[1..];
let mut groups = [None];
let mut workspace = [0x5a; 8];
let storage = MirxFontStorage::new(surfaces, output, &mut groups, &mut workspace)
.with_requirements(
SurfaceRequirements::new()
.with_base_alignment(mirx::ByteAlignment::new(64).unwrap())
.with_stride_multiple(64),
);
let provider =
MirxFontProvider::from_payload_with_storage(payload, &PayloadLimits::HOST, storage)
.unwrap();
for (ch, y) in [('A', 0), ('B', 2)] {
let glyph = provider.glyph(ch, 8).unwrap();
let GlyphKind::Raster {
samples,
stride,
region,
..
} = glyph.kind
else {
panic!("raster glyph");
};
assert_eq!(samples.as_ptr() as usize % 64, 0);
assert_eq!(samples.len(), 256);
assert_eq!(stride, 64);
assert_eq!(region, Region::new(0, y, 2, 2).unwrap());
for row in 0..4 {
assert_eq!(&samples[row * 64..row * 64 + 2], &[42; 2]);
assert!(
samples[row * 64 + 2..row * 64 + 64]
.iter()
.all(|byte| *byte == 0)
);
}
}
groups.fill(None);
workspace.fill(0);
}
#[test]
fn encoded_surface_admission_precedes_persistent_writes() {
#[repr(align(64))]
struct Short([u8; 255]);
let payload = encoded_face_payload();
let surfaces = alloc::boxed::Box::leak(alloc::boxed::Box::new([None]));
let surfaces_ptr = surfaces.as_ptr();
let output = &mut alloc::boxed::Box::leak(alloc::boxed::Box::new(Short([0xa5; 255]))).0;
let output_ptr = output.as_ptr();
let mut groups = [None];
let mut workspace = [0x5a; 8];
let storage = MirxFontStorage::new(surfaces, output, &mut groups, &mut workspace)
.with_requirements(
SurfaceRequirements::new()
.with_base_alignment(mirx::ByteAlignment::new(64).unwrap())
.with_stride_multiple(64),
);
assert!(matches!(
MirxFontProvider::from_payload_with_storage(payload, &PayloadLimits::HOST, storage),
Err(MirxFontError::SurfaceOutputTooSmall {
surface: 0,
needed: 256,
available: 255,
})
));
let output = unsafe { core::slice::from_raw_parts(output_ptr, 255) };
let surfaces = unsafe { core::slice::from_raw_parts(surfaces_ptr, 1) };
assert!(output.iter().all(|byte| *byte == 0xa5));
assert_eq!(surfaces, &[None]);
}
}