#![doc = include_str!("../../docs/glyph-maps.md")]
use core::iter::FusedIterator;
use crate::image::{Region, RegionError, TileGrid, TileGridError};
use crate::wire::{read_u32_le, write_u32_le};
pub const GLYPH_REGION_LEN: usize = 16;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum GlyphPacking {
GlyphMajor,
Atlas2D,
}
#[derive(Clone, Copy, Debug)]
enum MapSource<'a> {
Grid(TileGrid),
Atlas {
width: u32,
height: u32,
records: AtlasRecords<'a>,
},
}
#[derive(Clone, Copy, Debug)]
enum AtlasRecords<'a> {
Native(&'a [Region]),
Wire(&'a [u8]),
}
impl AtlasRecords<'_> {
fn len(self) -> usize {
match self {
Self::Native(regions) => regions.len(),
Self::Wire(bytes) => bytes.len() / GLYPH_REGION_LEN,
}
}
fn get(self, index: usize) -> Option<Result<Region, RegionError>> {
match self {
Self::Native(regions) => regions.get(index).copied().map(Ok),
Self::Wire(bytes) => {
if index >= self.len() {
return None;
}
let offset = index * GLYPH_REGION_LEN;
Some(Region::new(
read_u32_le(bytes, offset).unwrap(),
read_u32_le(bytes, offset + 4).unwrap(),
read_u32_le(bytes, offset + 8).unwrap(),
read_u32_le(bytes, offset + 12).unwrap(),
))
}
}
}
fn validate(self, width: u32, height: u32) -> Result<(), GlyphMapError> {
let size = self
.len()
.checked_mul(GLYPH_REGION_LEN)
.ok_or(GlyphMapError::SizeOverflow)?;
u32::try_from(size).map_err(|_| GlyphMapError::SizeOverflow)?;
for index in 0..self.len() {
let region = self
.get(index)
.expect("valid record index")
.map_err(|error| GlyphMapError::InvalidRegion { index, error })?;
if region.right() > width || region.bottom() > height {
return Err(GlyphMapError::InvalidRegion {
index,
error: RegionError::OutOfBounds,
});
}
}
Ok(())
}
}
#[derive(Clone, Copy, Debug)]
pub struct GlyphMap<'a> {
source: MapSource<'a>,
}
impl<'a> GlyphMap<'a> {
pub fn glyph_major(width: u32, height: u32, count: usize) -> Result<Self, GlyphMapError> {
let count = u32::try_from(count).map_err(|_| GlyphMapError::SizeOverflow)?;
let total_height = height
.checked_mul(count)
.ok_or(GlyphMapError::SizeOverflow)?;
let grid =
TileGrid::new(width, total_height, width, height).map_err(GlyphMapError::Grid)?;
Ok(Self {
source: MapSource::Grid(grid),
})
}
pub fn atlas(width: u32, height: u32, regions: &'a [Region]) -> Result<Self, GlyphMapError> {
Self::from_atlas(width, height, AtlasRecords::Native(regions))
}
pub fn from_records(width: u32, height: u32, bytes: &'a [u8]) -> Result<Self, GlyphMapError> {
u32::try_from(bytes.len()).map_err(|_| GlyphMapError::SizeOverflow)?;
if bytes.len() % GLYPH_REGION_LEN != 0 {
return Err(GlyphMapError::PartialRecord {
byte_len: bytes.len(),
});
}
Self::from_atlas(width, height, AtlasRecords::Wire(bytes))
}
fn from_atlas(
width: u32,
height: u32,
records: AtlasRecords<'a>,
) -> Result<Self, GlyphMapError> {
records.validate(width, height)?;
Ok(Self {
source: MapSource::Atlas {
width,
height,
records,
},
})
}
pub(super) fn from_validated_records(width: u32, height: u32, bytes: &'a [u8]) -> Self {
Self {
source: MapSource::Atlas {
width,
height,
records: AtlasRecords::Wire(bytes),
},
}
}
pub const fn packing(self) -> GlyphPacking {
match self.source {
MapSource::Grid(_) => GlyphPacking::GlyphMajor,
MapSource::Atlas { .. } => GlyphPacking::Atlas2D,
}
}
pub const fn width(self) -> u32 {
match self.source {
MapSource::Grid(grid) => grid.width(),
MapSource::Atlas { width, .. } => width,
}
}
pub const fn height(self) -> u32 {
match self.source {
MapSource::Grid(grid) => grid.height(),
MapSource::Atlas { height, .. } => height,
}
}
pub const fn cell_extent(self) -> Option<(u32, u32)> {
match self.source {
MapSource::Grid(grid) => Some((grid.tile_width(), grid.tile_height())),
MapSource::Atlas { .. } => None,
}
}
pub fn len(self) -> usize {
match self.source {
MapSource::Grid(grid) => grid.len(),
MapSource::Atlas { records, .. } => records.len(),
}
}
pub fn is_empty(self) -> bool {
self.len() == 0
}
pub fn get(self, index: usize) -> Option<Region> {
match self.source {
MapSource::Grid(grid) => grid.get(index),
MapSource::Atlas { records, .. } => records
.get(index)
.map(|r| r.expect("validated glyph region")),
}
}
pub fn iter(self) -> GlyphRegions<'a> {
GlyphRegions {
map: self,
front: 0,
back: self.len(),
}
}
pub fn encoded_len(self) -> usize {
match self.source {
MapSource::Grid(_) => 0,
MapSource::Atlas { records, .. } => records.len() * GLYPH_REGION_LEN,
}
}
pub fn encode_into(self, out: &mut [u8]) -> Result<usize, GlyphMapError> {
let needed = self.encoded_len();
if out.len() < needed {
return Err(GlyphMapError::BufferTooSmall {
needed,
available: out.len(),
});
}
match self.source {
MapSource::Grid(_) => {}
MapSource::Atlas {
records: AtlasRecords::Wire(bytes),
..
} => out[..needed].copy_from_slice(bytes),
MapSource::Atlas { .. } => {
for (record, region) in out[..needed].chunks_exact_mut(GLYPH_REGION_LEN).zip(self) {
write_u32_le(record, 0, region.x());
write_u32_le(record, 4, region.y());
write_u32_le(record, 8, region.width());
write_u32_le(record, 12, region.height());
}
}
}
Ok(needed)
}
}
impl<'a> IntoIterator for GlyphMap<'a> {
type Item = Region;
type IntoIter = GlyphRegions<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Clone, Debug)]
pub struct GlyphRegions<'a> {
map: GlyphMap<'a>,
front: usize,
back: usize,
}
impl Iterator for GlyphRegions<'_> {
type Item = Region;
fn next(&mut self) -> Option<Self::Item> {
if self.front == self.back {
return None;
}
let index = self.front;
self.front += 1;
self.map.get(index)
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
if n >= self.len() {
self.front = self.back;
return None;
}
self.front += n;
self.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len(), Some(self.len()))
}
fn count(self) -> usize {
self.len()
}
fn last(mut self) -> Option<Self::Item> {
self.next_back()
}
}
impl DoubleEndedIterator for GlyphRegions<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.front == self.back {
return None;
}
self.back -= 1;
self.map.get(self.back)
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
if n >= self.len() {
self.front = self.back;
return None;
}
self.back -= n;
self.next_back()
}
}
impl ExactSizeIterator for GlyphRegions<'_> {
fn len(&self) -> usize {
self.back - self.front
}
}
impl FusedIterator for GlyphRegions<'_> {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum GlyphMapError {
SizeOverflow,
Grid(TileGridError),
PartialRecord { byte_len: usize },
InvalidRegion { index: usize, error: RegionError },
BufferTooSmall { needed: usize, available: usize },
}
#[cfg(test)]
mod tests;