mirx 0.50.0

MIRX binary image format — flat & chunk-based container for ahead-of-time UI assets
Documentation
use super::{
    ADVANCE_RECORD_LEN, Advances, CmapIndex, CmapIndexError, FACE_RECORD_LEN, FontFace,
    FontFaceError, GLYPH_ID_RECORD_LEN, GlyphId, GlyphIds, GlyphIdsError,
    RASTER_METRICS_RECORD_LEN, RasterMetrics, RasterMetricsTable, RepresentationTable,
    RepresentationTableError, ShapingData, ShapingDataError,
};
use crate::{
    PayloadLimits,
    media::{MediaPayload, MediaPayloadError, MediaSection, MediaSectionFlags, MediaSectionKind},
    types::Fixed,
};

const FONT_SECTION_COUNT: usize = 9;

#[derive(Clone, Copy, Debug)]
pub struct FontMetadata<'a> {
    face: FontFace,
    cmap: CmapIndex<'a>,
    glyph_ids: Option<GlyphIds<'a>>,
    advances: Option<Advances<'a>>,
    shaping: Option<ShapingData<'a>>,
    representations: RepresentationTable<'a>,
    raster_metrics: RasterMetricsTable<'a>,
}

impl<'a> FontMetadata<'a> {
    pub fn open(bytes: &'a [u8], limits: &PayloadLimits) -> Result<Self, FontMetadataError> {
        let media = MediaPayload::open(bytes).map_err(FontMetadataError::Media)?;
        Self::open_media(media, limits)
    }

    pub(crate) fn open_media(
        media: MediaPayload<'a>,
        limits: &PayloadLimits,
    ) -> Result<Self, FontMetadataError> {
        let sections = Sections::open(media)?;
        let face =
            FontFace::from_record(sections.required(FACE)?).map_err(FontMetadataError::Face)?;
        let glyph_count = usize::from(face.raster_count());
        if glyph_count > limits.max_font_glyphs() as usize {
            return Err(FontMetadataError::TooManyGlyphs {
                limit: limits.max_font_glyphs(),
                actual: glyph_count,
            });
        }

        let cmap = CmapIndex::open(sections.required(CMAP)?).map_err(FontMetadataError::Cmap)?;
        if cmap.len() > limits.max_font_cmap_entries() as usize {
            return Err(FontMetadataError::TooManyCmapEntries {
                limit: limits.max_font_cmap_entries(),
                actual: cmap.len(),
            });
        }

        let glyph_ids = sections
            .optional(GLYPH_IDS)
            .map(GlyphIds::open)
            .transpose()
            .map_err(FontMetadataError::GlyphIds)?;
        if let Some(glyph_ids) = glyph_ids
            && glyph_ids.len() != glyph_count
        {
            return Err(FontMetadataError::GlyphIdCount {
                expected: glyph_count,
                actual: glyph_ids.len(),
            });
        }

        let advances = sections
            .optional(ADVANCES)
            .map(Advances::open)
            .transpose()
            .map_err(FontMetadataError::Placement)?;
        let shaping = sections
            .optional(SHAPING)
            .map(ShapingData::open)
            .transpose()
            .map_err(FontMetadataError::Shaping)?;
        match (advances, shaping) {
            (Some(_), Some(_)) => return Err(FontMetadataError::ConflictingAdvanceSources),
            (None, None) => return Err(FontMetadataError::MissingAdvanceSource),
            _ => {}
        }
        if let Some(advances) = advances
            && advances.len() != glyph_count
        {
            return Err(FontMetadataError::AdvanceCount {
                expected: glyph_count,
                actual: advances.len(),
            });
        }
        if let Some(shaping) = shaping {
            shaping
                .preflight(limits)
                .map_err(FontMetadataError::Shaping)?;
            shaping
                .validate_cmap(cmap)
                .map_err(FontMetadataError::Shaping)?;
        }

        let representations = RepresentationTable::open(
            sections.required(REPRESENTATIONS)?,
            sections.required(SURFACE_GROUPS)?,
            glyph_count,
            limits,
        )
        .map_err(FontMetadataError::Representations)?;
        if representations.is_empty() {
            return Err(FontMetadataError::EmptyRepresentations);
        }
        let raster_metrics = RasterMetricsTable::open(sections.required(RASTER_METRICS)?)
            .map_err(FontMetadataError::Placement)?;
        let expected_metrics = representations
            .len()
            .checked_mul(glyph_count)
            .ok_or(FontMetadataError::SizeOverflow)?;
        if raster_metrics.len() != expected_metrics {
            return Err(FontMetadataError::RasterMetricCount {
                expected: expected_metrics,
                actual: raster_metrics.len(),
            });
        }

        let metadata = Self {
            face,
            cmap,
            glyph_ids,
            advances,
            shaping,
            representations,
            raster_metrics,
        };
        metadata.require_raster(face.default_glyph())?;
        for entry in cmap.iter() {
            metadata.require_raster(entry.glyph_id())?;
        }
        Ok(metadata)
    }

    pub const fn face(self) -> FontFace {
        self.face
    }

    pub const fn cmap(self) -> CmapIndex<'a> {
        self.cmap
    }

    pub const fn glyph_ids(self) -> Option<GlyphIds<'a>> {
        self.glyph_ids
    }

    pub const fn advances(self) -> Option<Advances<'a>> {
        self.advances
    }

    pub const fn shaping_data(self) -> Option<ShapingData<'a>> {
        self.shaping
    }

    pub const fn representations(self) -> RepresentationTable<'a> {
        self.representations
    }

    pub const fn glyph_count(self) -> usize {
        self.face.raster_count() as usize
    }

    pub fn map_char(self, scalar: char) -> Option<GlyphId> {
        self.cmap.lookup(scalar)
    }

    pub fn glyph_id(self, ordinal: usize) -> Option<GlyphId> {
        match self.glyph_ids {
            Some(glyph_ids) => glyph_ids.get(ordinal),
            None => u16::try_from(ordinal)
                .ok()
                .filter(|_| ordinal < usize::from(self.face.raster_count()))
                .map(GlyphId::new),
        }
    }

    pub fn raster_ordinal(self, glyph_id: GlyphId) -> Option<usize> {
        match self.glyph_ids {
            Some(glyph_ids) => glyph_ids.ordinal(glyph_id).ok(),
            None => {
                let ordinal = usize::from(glyph_id.get());
                (ordinal < usize::from(self.face.raster_count())).then_some(ordinal)
            }
        }
    }

    pub fn advance(self, glyph_id: GlyphId) -> Option<Fixed> {
        self.advances?.get(self.raster_ordinal(glyph_id)?)
    }

    pub fn raster_metrics(self, representation: usize, glyph_id: GlyphId) -> Option<RasterMetrics> {
        let ordinal = self.raster_ordinal(glyph_id)?;
        let index = representation
            .checked_mul(usize::from(self.face.raster_count()))?
            .checked_add(ordinal)?;
        self.raster_metrics.get(index)
    }

    fn require_raster(self, glyph_id: GlyphId) -> Result<(), FontMetadataError> {
        if self.raster_ordinal(glyph_id).is_some() {
            Ok(())
        } else {
            Err(FontMetadataError::MissingRasterGlyph(glyph_id))
        }
    }
}

const FACE: usize = 0;
const CMAP: usize = 1;
const REPRESENTATIONS: usize = 2;
const ADVANCES: usize = 3;
const ATLAS_MAPS: usize = 4;
const SURFACE_GROUPS: usize = 5;
const GLYPH_IDS: usize = 6;
const RASTER_METRICS: usize = 7;
const SHAPING: usize = 8;

struct Sections<'a> {
    slots: [Option<MediaSection<'a>>; FONT_SECTION_COUNT],
}

impl<'a> Sections<'a> {
    fn open(media: MediaPayload<'a>) -> Result<Self, FontMetadataError> {
        let mut sections = Self {
            slots: [None; FONT_SECTION_COUNT],
        };
        for section in media.sections() {
            let descriptor = section.descriptor();
            let kind = descriptor.kind();
            let slot = match kind {
                MediaSectionKind::FACE => Some(FACE),
                MediaSectionKind::CMAP_INDEX => Some(CMAP),
                MediaSectionKind::REPRESENTATIONS => Some(REPRESENTATIONS),
                MediaSectionKind::ADVANCES => Some(ADVANCES),
                MediaSectionKind::ATLAS_MAPS => Some(ATLAS_MAPS),
                MediaSectionKind::SURFACE_GROUPS => Some(SURFACE_GROUPS),
                MediaSectionKind::GLYPH_IDS => Some(GLYPH_IDS),
                MediaSectionKind::RASTER_METRICS => Some(RASTER_METRICS),
                MediaSectionKind::SHAPING => Some(SHAPING),
                _ => None,
            };
            let known = slot.is_some() || Self::is_storage(kind);
            if known && descriptor.flags() != MediaSectionFlags::REQUIRED {
                return Err(FontMetadataError::SectionFlags(kind));
            }
            if !known && descriptor.flags().is_required() {
                return Err(FontMetadataError::UnknownRequiredSection(kind));
            }
            if let Some(slot) = slot
                && sections.slots[slot].replace(section).is_some()
            {
                return Err(FontMetadataError::DuplicateSection(kind));
            }
        }
        Ok(sections)
    }

    fn required(&self, slot: usize) -> Result<&'a [u8], FontMetadataError> {
        self.optional(slot)
            .ok_or(FontMetadataError::MissingSection(section_kind(slot)))
    }

    fn optional(&self, slot: usize) -> Option<&'a [u8]> {
        self.slots[slot].map(MediaSection::bytes)
    }

    fn is_storage(kind: MediaSectionKind) -> bool {
        matches!(
            kind,
            MediaSectionKind::PLANES
                | MediaSectionKind::CODINGS
                | MediaSectionKind::UNIT_GROUPS
                | MediaSectionKind::UNIT_INDEX
                | MediaSectionKind::DATA
                | MediaSectionKind::INTEGRITY
        )
    }
}

fn section_kind(slot: usize) -> MediaSectionKind {
    [
        MediaSectionKind::FACE,
        MediaSectionKind::CMAP_INDEX,
        MediaSectionKind::REPRESENTATIONS,
        MediaSectionKind::ADVANCES,
        MediaSectionKind::ATLAS_MAPS,
        MediaSectionKind::SURFACE_GROUPS,
        MediaSectionKind::GLYPH_IDS,
        MediaSectionKind::RASTER_METRICS,
        MediaSectionKind::SHAPING,
    ][slot]
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum FontMetadataError {
    Media(MediaPayloadError),
    Face(FontFaceError),
    Cmap(CmapIndexError),
    GlyphIds(GlyphIdsError),
    Placement(super::PlacementError),
    Shaping(ShapingDataError),
    Representations(RepresentationTableError),
    MissingSection(MediaSectionKind),
    DuplicateSection(MediaSectionKind),
    UnknownRequiredSection(MediaSectionKind),
    SectionFlags(MediaSectionKind),
    TooManyGlyphs { limit: u32, actual: usize },
    TooManyCmapEntries { limit: u32, actual: usize },
    GlyphIdCount { expected: usize, actual: usize },
    AdvanceCount { expected: usize, actual: usize },
    RasterMetricCount { expected: usize, actual: usize },
    MissingAdvanceSource,
    ConflictingAdvanceSources,
    MissingRasterGlyph(GlyphId),
    EmptyRepresentations,
    SizeOverflow,
}

const _: () = {
    assert!(FACE_RECORD_LEN == 20);
    assert!(ADVANCE_RECORD_LEN == 4);
    assert!(GLYPH_ID_RECORD_LEN == 2);
    assert!(RASTER_METRICS_RECORD_LEN == 8);
};

#[cfg(test)]
mod tests;