rari 0.13.6

rari: Runtime Accelerated Rendering Infrastructure - A high-performance React Server Components framework powered by Rust
Documentation
use parley::{
    FontContext as ParleyFontContext, GenericFamily,
    fontique::{Blob, Collection, CollectionOptions, FallbackKey, Script, ScriptExt},
};
use std::borrow::Cow;
use std::sync::Arc;
use thiserror::Error;

const TWEMOJI_FONT: &[u8] = include_bytes!("fonts/TwemojiMozilla-colr.woff2");
const NOTO_SANS_FONT: &[u8] = include_bytes!("fonts/NotoSansTC-VariableFont_wght.woff2");

#[derive(Debug, Error)]
pub enum FontError {
    #[error("Error occurred during WOFF2 decompression: {0}")]
    Woff2(String),
    #[error("Unsupported font format")]
    UnsupportedFormat,
}

#[derive(Copy, Clone)]
enum FontFormat {
    Woff2,
    Woff,
    Ttf,
    Otf,
}

pub fn load_font(source: &[u8]) -> Result<Cow<'_, [u8]>, FontError> {
    let format = guess_font_format(source)?;

    match format {
        FontFormat::Ttf | FontFormat::Otf => Ok(Cow::Borrowed(source)),
        FontFormat::Woff2 => {
            let ttf =
                wuff::decompress_woff2(source).map_err(|e| FontError::Woff2(format!("{:?}", e)))?;
            Ok(Cow::Owned(ttf))
        }
        FontFormat::Woff => {
            let ttf =
                wuff::decompress_woff1(source).map_err(|e| FontError::Woff2(format!("{:?}", e)))?;
            Ok(Cow::Owned(ttf))
        }
    }
}

fn guess_font_format(source: &[u8]) -> Result<FontFormat, FontError> {
    if source.len() < 4 {
        return Err(FontError::UnsupportedFormat);
    }

    match &source[0..4] {
        b"wOF2" => Ok(FontFormat::Woff2),
        b"wOFF" => Ok(FontFormat::Woff),
        [0x00, 0x01, 0x00, 0x00] => Ok(FontFormat::Ttf),
        b"OTTO" => Ok(FontFormat::Otf),
        _ => Err(FontError::UnsupportedFormat),
    }
}

#[derive(Clone)]
pub struct FontContext {
    pub(crate) inner: ParleyFontContext,
}

impl Default for FontContext {
    fn default() -> Self {
        Self::new()
    }
}

impl FontContext {
    pub fn new() -> Self {
        let inner = ParleyFontContext {
            collection: Collection::new(CollectionOptions { system_fonts: false, shared: false }),
            source_cache: Default::default(),
        };

        let mut ctx = Self { inner };

        if let Err(e) = ctx.load_twemoji() {
            eprintln!("Warning: Failed to load Twemoji font: {:?}", e);
        }

        if let Err(e) = ctx.load_default_font() {
            eprintln!("Warning: Failed to load default font: {:?}", e);
        }

        ctx
    }

    fn load_default_font(&mut self) -> Result<(), FontError> {
        let font_data = load_font(NOTO_SANS_FONT)?;
        let blob = Blob::new(Arc::new(font_data.into_owned()));

        let fonts = self.inner.collection.register_fonts(blob, None);

        for (family, _) in fonts {
            self.inner
                .collection
                .append_generic_families(GenericFamily::SansSerif, std::iter::once(family));
        }

        Ok(())
    }

    fn load_twemoji(&mut self) -> Result<(), FontError> {
        let font_data = load_font(TWEMOJI_FONT)?;
        let blob = Blob::new(Arc::new(font_data.into_owned()));

        let fonts = self.inner.collection.register_fonts(blob, None);

        for (family, _) in fonts {
            self.inner
                .collection
                .append_generic_families(GenericFamily::Emoji, std::iter::once(family));

            for (script, _) in Script::all_samples() {
                self.inner
                    .collection
                    .append_fallbacks(FallbackKey::new(*script, None), std::iter::once(family));
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_font_context_creation() {
        let _ctx = FontContext::new();
    }

    #[test]
    fn test_load_twemoji() {
        let _ctx = FontContext::new();
    }

    #[test]
    fn test_emoji_text_layout() {
        use parley::{LayoutContext, TextStyle};

        let ctx = FontContext::new();
        let mut font_ctx = ctx.inner.clone();
        let mut layout_cx: LayoutContext<[u8; 4]> = LayoutContext::new();

        let root_style = TextStyle { font_size: 32.0, ..Default::default() };

        let mut builder = layout_cx.tree_builder(&mut font_ctx, 1.0, true, &root_style);
        builder.push_text("Hello 🎉 World");
        let (mut layout, _text) = builder.build();
        layout.break_all_lines(Some(500.0));

        let line_count = layout.lines().count();
        assert!(line_count > 0, "Should have at least one line");

        let mut glyph_count = 0;
        for line in layout.lines() {
            for item in line.items() {
                if let parley::PositionedLayoutItem::GlyphRun(gr) = item {
                    glyph_count += gr.positioned_glyphs().count();
                }
            }
        }

        println!("Line count: {}, Glyph count: {}", line_count, glyph_count);
        assert!(glyph_count > 0, "Should have glyphs");
    }

    #[test]
    fn test_emoji_glyph_rendering() {
        use parley::{LayoutContext, TextStyle};
        use swash::{FontRef, scale::ScaleContext};

        let ctx = FontContext::new();
        let mut font_ctx = ctx.inner.clone();
        let mut layout_cx: LayoutContext<[u8; 4]> = LayoutContext::new();

        let root_style = TextStyle { font_size: 32.0, ..Default::default() };

        let mut builder = layout_cx.tree_builder(&mut font_ctx, 1.0, true, &root_style);
        builder.push_text("🎉");
        let (mut layout, _text) = builder.build();
        layout.break_all_lines(Some(500.0));

        let mut scale_context = ScaleContext::new();

        for line in layout.lines() {
            for item in line.items() {
                if let parley::PositionedLayoutItem::GlyphRun(gr) = item {
                    let run = gr.run();
                    let font_ref =
                        FontRef::from_index(run.font().data.as_ref(), run.font().index as usize)
                            .expect("Invalid font index");

                    println!("Font has {} color palettes", font_ref.color_palettes().count());

                    let mut scaler = scale_context
                        .builder(font_ref)
                        .size(32.0)
                        .normalized_coords(run.normalized_coords())
                        .build();

                    for glyph in gr.positioned_glyphs() {
                        println!("Glyph ID: {}", glyph.id);

                        if let Some(bitmap) = scaler
                            .scale_color_bitmap(glyph.id as u16, swash::scale::StrikeWith::BestFit)
                        {
                            println!(
                                "  -> Color bitmap: {}x{}",
                                bitmap.placement.width, bitmap.placement.height
                            );
                        } else if let Some(outline) = scaler.scale_color_outline(glyph.id as u16) {
                            println!(
                                "  -> Color outline with {} layers, is_color: {}",
                                outline.len(),
                                outline.is_color()
                            );
                        } else if let Some(_outline) = scaler.scale_outline(glyph.id as u16) {
                            println!("  -> Regular outline");
                        } else {
                            println!("  -> NO GLYPH DATA!");
                        }
                    }
                }
            }
        }
    }

    #[test]
    fn test_guess_font_format() {
        assert!(matches!(guess_font_format(b"wOF2...."), Ok(FontFormat::Woff2)));
        assert!(matches!(guess_font_format(b"wOFF...."), Ok(FontFormat::Woff)));
        assert!(matches!(guess_font_format(&[0x00, 0x01, 0x00, 0x00]), Ok(FontFormat::Ttf)));
        assert!(matches!(guess_font_format(b"OTTO"), Ok(FontFormat::Otf)));
        assert!(matches!(guess_font_format(b"invalid"), Err(FontError::UnsupportedFormat)));
        assert!(matches!(guess_font_format(b"abc"), Err(FontError::UnsupportedFormat)));
    }
}