use crate::backend::render::{RenderBackend, ShapeHandle}; use crate::prelude::*; use crate::transform::Transform; use gc_arena::{Collect, Gc, MutationContext}; type Error = Box; #[derive(Debug, Clone, Collect, Copy)] #[collect(no_drop)] pub struct Font<'gc>(Gc<'gc, FontData>); #[derive(Debug, Clone, Collect)] #[collect(require_static)] struct FontData { /// The list of glyphs defined in the font. /// Used directly by `DefineText` tags. glyphs: Vec, /// A map from a Unicode code point to glyph in the `glyphs` array. /// Used by `DefineEditText` tags. code_point_to_glyph: fnv::FnvHashMap, /// The scaling applied to the font height to render at the proper size. /// This depends on the DefineFont tag version. scale: f32, /// Kerning infomration. /// Maps from a pair of unicode code points to horizontal offset value. kerning_pairs: fnv::FnvHashMap<(u16, u16), Twips>, } impl<'gc> Font<'gc> { pub fn from_swf_tag( gc_context: MutationContext<'gc, '_>, renderer: &mut dyn RenderBackend, tag: &swf::Font, ) -> Result, Error> { let mut glyphs = vec![]; let mut code_point_to_glyph = fnv::FnvHashMap::default(); for swf_glyph in &tag.glyphs { let glyph = Glyph { shape: renderer.register_glyph_shape(swf_glyph), advance: swf_glyph.advance.unwrap_or(0), }; let index = glyphs.len(); glyphs.push(glyph); code_point_to_glyph.insert(swf_glyph.code, index); } let kerning_pairs: fnv::FnvHashMap<(u16, u16), Twips> = if let Some(layout) = &tag.layout { layout .kerning .iter() .map(|kerning| ((kerning.left_code, kerning.right_code), kerning.adjustment)) .collect() } else { fnv::FnvHashMap::default() }; Ok(Font(Gc::allocate( gc_context, FontData { glyphs, code_point_to_glyph, /// DefineFont3 stores coordinates at 20x the scale of DefineFont1/2. /// (SWF19 p.164) scale: if tag.version >= 3 { 20480.0 } else { 1024.0 }, kerning_pairs, }, ))) } /// Returns whether this font contains glyph shapes. /// If not, this font should be rendered as a device font. pub fn has_glyphs(self) -> bool { !self.0.glyphs.is_empty() } /// Returns a glyph entry by index. /// Used by `Text` display objects. pub fn get_glyph(self, i: usize) -> Option { self.0.glyphs.get(i).cloned() } /// Returns a glyph entry by character. /// Used by `EditText` display objects. pub fn get_glyph_for_char(self, c: char) -> Option { // TODO: Properly handle UTF-16/out-of-bounds code points. let code_point = c as u16; if let Some(index) = self.0.code_point_to_glyph.get(&code_point) { self.get_glyph(*index) } else { None } } /// Given a pair of characters, applies the offset that should be applied /// to the advance value between these two characters. /// Returns 0 twips if no kerning offset exists between these two characters. pub fn get_kerning_offset(self, left: char, right: char) -> Twips { // TODO: Properly handle UTF-16/out-of-bounds code points. let left_code_point = left as u16; let right_code_point = right as u16; self.0 .kerning_pairs .get(&(left_code_point, right_code_point)) .cloned() .unwrap_or_default() } /// Returns whether this font contains kerning information. pub fn has_kerning_info(self) -> bool { !self.0.kerning_pairs.is_empty() } pub fn scale(self) -> f32 { self.0.scale } /// Evaluate this font against a particular string on a glyph-by-glyph /// basis. /// /// This function takes the text string to evaluate against, the base /// transform to start from, the height of each glyph, and produces a list /// of transforms and glyphs which will be consumed by the `glyph_func` /// closure. This corresponds to the series of drawing operations necessary /// to render the text on a single horizontal line. pub fn evaluate( self, text: &str, mut transform: Transform, height: f32, is_html: bool, mut glyph_func: FGlyph, ) where FGlyph: FnMut(&Transform, &Glyph), { transform.matrix.ty += height; let scale = height / self.scale(); transform.matrix.a = scale; transform.matrix.d = scale; let mut chars = text.chars().peekable(); let has_kerning_info = self.has_kerning_info(); while let Some(c) = chars.next() { // TODO: SWF text fields can contain a limited subset of HTML (and often do in SWF versions >6). // This is a quicky-and-dirty way to skip the HTML tags. This is obviously not correct // and we will need to properly parse and handle the HTML at some point. // See SWF19 pp. 173-174 for supported HTML tags. if is_html && c == '<' { // Skip characters until we see a close bracket. chars.by_ref().skip_while(|&x| x != '>').next(); } else if let Some(glyph) = self.get_glyph_for_char(c) { glyph_func(&transform, &glyph); // Step horizontally. let mut advance = f32::from(glyph.advance); if has_kerning_info { advance += self .get_kerning_offset(c, chars.peek().cloned().unwrap_or('\0')) .get() as f32; } transform.matrix.tx += advance * scale; } } } } #[derive(Debug, Clone)] pub struct Glyph { pub shape: ShapeHandle, pub advance: i16, }