StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | // Neither macOS nor wasm make use of the "load font from path" functionality, |
| 2 | // and so there's a lot of unused code in here. Instead of marking each of the |
| 3 | // relevant functions with allow(dead_code), we'll do it at the module level |
| 4 | // instead for simplicity. |
| 5 | #![cfg_attr( |
| 6 | any(target_os = "macos", target_os = "windows", target_family = "wasm"), |
| 7 | allow(dead_code) |
| 8 | )] |
| 9 | |
| 10 | use owned_ttf_parser::{AsFaceRef, Face, FaceParsingError, OwnedFace}; |
| 11 | use std::fs::File; |
| 12 | use std::path::PathBuf; |
| 13 | |
| 14 | /// A handle that wraps around a font face. |
| 15 | pub struct FontHandle { |
| 16 | data: FontData, |
| 17 | } |
| 18 | |
| 19 | /// Source data for a font to be loaded within the winit font system. |
| 20 | pub enum FontData { |
| 21 | /// The font is to be loaded via bytes. This should be used sparingly since it requires loading the font into |
| 22 | /// memory. |
| 23 | Bytes(OwnedFace), |
| 24 | /// The font identified at the given `path` and `index` will be loaded. |
| 25 | /// NOTE the font will never be loaded into memory. Instead, data from the font will be read via a memory-mapped |
| 26 | /// file. |
| 27 | Path { |
| 28 | path: PathBuf, |
| 29 | index: u32, |
| 30 | is_monospace: bool, |
| 31 | }, |
| 32 | } |
| 33 | |
| 34 | impl FontData { |
| 35 | /// Returns an [`Error`] if the [`FontData`] does not map to a valid font. |
| 36 | /// |
| 37 | /// A font is considered valid iff: |
| 38 | /// * The file referenced by [`FontData::Path`] exists and can be read. |
| 39 | /// * The data can be parsed into a valid [`ttf_parser::Face`]. |
| 40 | /// * The font face contains a glyph for the 'm' character. |
| 41 | fn validate(&self) -> Result<(), Error> { |
| 42 | match self { |
| 43 | FontData::Bytes(_) => Ok(()), |
| 44 | FontData::Path { path, index, .. } => { |
| 45 | let file = File::open(path).map_err(|e| Error::Load { |
| 46 | path: path.clone(), |
| 47 | io_error: e, |
| 48 | })?; |
| 49 | let mmap = unsafe { |
| 50 | memmap2::Mmap::map(&file).map_err(|e| Error::Load { |
| 51 | path: path.clone(), |
| 52 | io_error: e, |
| 53 | })? |
| 54 | }; |
| 55 | let face = Face::parse(&mmap, *index).map_err(|e| Error::Parse { |
| 56 | path: path.clone(), |
| 57 | parse_error: e, |
| 58 | })?; |
| 59 | |
| 60 | if face.as_face_ref().glyph_index('m').is_none() { |
| 61 | Err(Error::Validate { path: path.clone() }) |
| 62 | } else { |
| 63 | Ok(()) |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | impl FontHandle { |
| 71 | pub fn new(path: impl Into<PathBuf>, index: u32, is_monospace: bool) -> Self { |
| 72 | Self { |
| 73 | data: FontData::Path { |
| 74 | path: path.into(), |
| 75 | index, |
| 76 | is_monospace, |
| 77 | }, |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | pub fn is_monospace(&self) -> bool { |
| 82 | match &self.data { |
| 83 | FontData::Path { is_monospace, .. } => *is_monospace, |
| 84 | FontData::Bytes(face) => face.as_face_ref().is_monospaced(), |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | /// Validates the [`FontHandle`] is a parseable font. |
| 89 | pub fn validate_font_data(&self) -> Result<(), Error> { |
| 90 | self.data.validate() |
| 91 | } |
| 92 | |
| 93 | pub(super) fn into_data(self) -> FontData { |
| 94 | self.data |
| 95 | } |
| 96 | |
| 97 | #[allow(dead_code)] |
| 98 | pub(super) fn data(&self) -> &FontData { |
| 99 | &self.data |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | impl From<OwnedFace> for FontHandle { |
| 104 | fn from(value: OwnedFace) -> Self { |
| 105 | Self { |
| 106 | data: FontData::Bytes(value), |
| 107 | } |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | /// Errors associated with loading fonts |
| 112 | #[derive(Debug, thiserror::Error)] |
| 113 | pub enum Error { |
| 114 | /// Failed to load font data due to an underlying std::io::Error |
| 115 | #[error("Error loading font data for font {path}")] |
| 116 | Load { |
| 117 | path: PathBuf, |
| 118 | io_error: std::io::Error, |
| 119 | }, |
| 120 | |
| 121 | /// Failed to parse the underlying data into a valid font |
| 122 | #[error("Error parsing font data for font {path}")] |
| 123 | Parse { |
| 124 | path: PathBuf, |
| 125 | parse_error: FaceParsingError, |
| 126 | }, |
| 127 | |
| 128 | /// A font was properly loaded, but did not have a codepoint |
| 129 | /// for the letter m, indicating it would not work within Warp. |
| 130 | #[error("Font {path} does not have a valid codepoint for the letter m")] |
| 131 | Validate { path: PathBuf }, |
| 132 | } |
| 133 |