diff --git a/core/src/lib.rs b/core/src/lib.rs index bbdb0c803..ffec056b8 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -71,3 +71,4 @@ pub use player::{Player, PlayerBuilder, PlayerRuntime, StaticCallstack}; pub use ruffle_render::backend::ViewportDimensions; pub use swf; pub use swf::Color; +pub use ttf_parser; diff --git a/core/src/library.rs b/core/src/library.rs index 6d9fe18cc..8cadb246e 100644 --- a/core/src/library.rs +++ b/core/src/library.rs @@ -595,7 +595,9 @@ impl<'gc> Library<'gc> { let font = Font::from_swf_tag(gc_context, renderer, tag, encoding, FontType::Device); let name = font.descriptor().name().to_owned(); - info!("Loaded new device font \"{name}\" from swf tag"); + let is_bold = font.descriptor().bold(); + let is_italic = font.descriptor().italic(); + info!("Loaded new device font \"{name}\" (bold: {is_bold}, italic: {is_italic}) from swf tag"); self.device_fonts.register(font); } FontDefinition::FontFile { @@ -614,7 +616,7 @@ impl<'gc> Library<'gc> { FontType::Device, ) { let name = font.descriptor().name().to_owned(); - info!("Loaded new device font \"{name}\" from file"); + info!("Loaded new device font \"{name}\" (bold: {is_bold}, italic: {is_italic}) from file"); self.device_fonts.register(font); } else { warn!("Failed to load device font from file"); diff --git a/web/src/builder.rs b/web/src/builder.rs index 176dd6abb..10be813cc 100644 --- a/web/src/builder.rs +++ b/web/src/builder.rs @@ -11,6 +11,7 @@ use ruffle_core::backend::storage::{MemoryStorageBackend, StorageBackend}; use ruffle_core::backend::ui::FontDefinition; use ruffle_core::compatibility_rules::CompatibilityRules; use ruffle_core::config::{Letterbox, NetworkingAccessMode}; +use ruffle_core::ttf_parser; use ruffle_core::{ swf, Color, DefaultFont, Player, PlayerBuilder, PlayerRuntime, StageAlign, StageScaleMode, }; @@ -339,46 +340,71 @@ impl RuffleInstanceBuilder { impl RuffleInstanceBuilder { pub fn setup_fonts(&self, player: &mut Player) { for (font_name, bytes) in &self.custom_fonts { - if let Ok(swf_stream) = swf::decompress_swf(&bytes[..]) { - if let Ok(swf) = swf::parse_swf(&swf_stream) { - let encoding = swf::SwfStr::encoding_for_version(swf.header.version()); - for tag in swf.tags { - match tag { - swf::Tag::DefineFont(_font) => { - tracing::warn!("DefineFont1 tag is not yet supported by Ruffle, inside font swf {font_name}"); - } - swf::Tag::DefineFont2(font) => { - tracing::debug!( - "Loaded font {} from font swf {font_name}", - font.name.to_str_lossy(encoding) - ); - player - .register_device_font(FontDefinition::SwfTag(*font, encoding)); - } - swf::Tag::DefineFont4(font) => { - let name = font.name.to_str_lossy(encoding); - if let Some(data) = font.data { - tracing::debug!("Loaded font {name} from font swf {font_name}"); - player.register_device_font(FontDefinition::FontFile { - name: name.to_string(), - is_bold: font.is_bold, - is_italic: font.is_italic, - data: data.to_vec(), - index: 0, - }) - } else { - tracing::warn!( - "Font {name} from font swf {font_name} contains no data" - ); - } - } - _ => {} - } + let bytes_slice = &bytes[..]; + if let Ok(face) = ttf_parser::Face::parse(bytes_slice, 0) { + tracing::debug!("Loading font {font_name} as TTF/OTF/TTC/OTC font"); + + // Check if font collection + let number_of_fonts = ttf_parser::fonts_in_collection(bytes_slice).unwrap_or(1u32); + + Self::register_ttf_face_by_name(font_name, bytes.clone(), face, 0, player); + + // Register all remaining fonts in the collection if it is a collection + for i in 1u32..number_of_fonts { + if let Ok(face) = ttf_parser::Face::parse(bytes_slice, i) { + Self::register_ttf_face_by_name(font_name, bytes.clone(), face, i, player); + } else { + tracing::warn!( + "Failed to parse font {font_name} at index {i} in font collection" + ); } - continue; } + } else { + tracing::debug!("Loading font {font_name} as SWF font"); + if let Ok(swf_stream) = swf::decompress_swf(&bytes[..]) { + if let Ok(swf) = swf::parse_swf(&swf_stream) { + let encoding = swf::SwfStr::encoding_for_version(swf.header.version()); + for tag in swf.tags { + match tag { + swf::Tag::DefineFont(_font) => { + tracing::warn!("DefineFont1 tag is not yet supported by Ruffle, inside font swf {font_name}"); + } + swf::Tag::DefineFont2(font) => { + tracing::debug!( + "Loaded font {} from font swf {font_name}", + font.name.to_str_lossy(encoding) + ); + player.register_device_font(FontDefinition::SwfTag( + *font, encoding, + )); + } + swf::Tag::DefineFont4(font) => { + let name = font.name.to_str_lossy(encoding); + if let Some(data) = font.data { + tracing::debug!( + "Loaded font {name} from font swf {font_name}" + ); + player.register_device_font(FontDefinition::FontFile { + name: name.to_string(), + is_bold: font.is_bold, + is_italic: font.is_italic, + data: data.to_vec(), + index: 0, + }) + } else { + tracing::warn!( + "Font {name} from font swf {font_name} contains no data" + ); + } + } + _ => {} + } + } + continue; + } + } + tracing::warn!("Font source {font_name} was not recognised (not a valid SWF?)"); } - tracing::warn!("Font source {font_name} was not recognised (not a valid SWF?)"); } for (default, names) in &self.default_fonts { @@ -386,6 +412,36 @@ impl RuffleInstanceBuilder { } } + #[inline] + fn register_ttf_face_by_name( + url: &String, + bytes: Vec, + face: ttf_parser::Face<'_>, + index: u32, + player: &mut Player, + ) { + let full_name = face + .names() + .into_iter() + .find(|name| name.name_id == ttf_parser::name_id::FULL_NAME) + .and_then(|name| name.to_string()); + + let name = if let Some(full_name) = full_name { + full_name + } else { + tracing::warn!("Font {url} at index {index} has no full name, using URL as font name"); + url.to_string() + }; + + player.register_device_font(FontDefinition::FontFile { + name: name.to_string(), + is_bold: face.is_bold(), + is_italic: face.is_italic(), + data: bytes, + index, + }); + } + pub fn create_log_subscriber(&self) -> Arc> { let layer = WASMLayer::new( WASMLayerConfigBuilder::new()