ruffle/core/src/library.rs

171 lines
5.7 KiB
Rust
Raw Normal View History

use crate::avm1::globals::SystemPrototypes;
use crate::avm1::Object;
2019-05-05 22:55:27 +00:00
use crate::backend::audio::SoundHandle;
2019-04-25 17:52:22 +00:00
use crate::character::Character;
use crate::display_object::TDisplayObject;
2019-05-04 18:45:11 +00:00
use crate::font::Font;
use crate::prelude::*;
use gc_arena::MutationContext;
2019-04-25 17:52:22 +00:00
use std::collections::HashMap;
use swf::CharacterId;
2019-05-24 17:25:03 +00:00
pub struct Library<'gc> {
characters: HashMap<CharacterId, Character<'gc>>,
2019-12-16 19:29:32 +00:00
export_characters: HashMap<String, Character<'gc>>,
jpeg_tables: Option<Vec<u8>>,
2019-12-17 05:21:59 +00:00
device_font: Option<Font<'gc>>,
2019-04-25 17:52:22 +00:00
}
2019-05-24 17:25:03 +00:00
impl<'gc> Library<'gc> {
pub fn new() -> Self {
2019-04-25 17:52:22 +00:00
Library {
characters: HashMap::new(),
2019-12-16 19:29:32 +00:00
export_characters: HashMap::new(),
jpeg_tables: None,
device_font: None,
2019-04-25 17:52:22 +00:00
}
}
2019-05-24 17:25:03 +00:00
pub fn register_character(&mut self, id: CharacterId, character: Character<'gc>) {
2019-04-25 17:52:22 +00:00
// TODO(Herschel): What is the behavior if id already exists?
2019-07-19 08:32:41 +00:00
if !self.contains_character(id) {
self.characters.insert(id, character);
} else {
log::error!("Character ID collision: Tried to register ID {} twice", id);
}
2019-04-25 17:52:22 +00:00
}
2019-12-16 19:29:32 +00:00
/// Registers an export name for a given character ID.
/// This character will then be instantiable from AVM1.
pub fn register_export(&mut self, id: CharacterId, export_name: &str) {
use std::collections::hash_map::Entry;
if let Some(character) = self.characters.get(&id) {
match self.export_characters.entry(export_name.to_string()) {
Entry::Vacant(e) => {
e.insert(character.clone());
}
Entry::Occupied(_) => {
log::warn!(
"Can't register export {}: Export already exists",
export_name
);
}
}
} else {
log::warn!(
"Can't register export {}: Character ID {} doesn't exist",
export_name,
id
)
}
}
2019-04-25 17:52:22 +00:00
pub fn contains_character(&self, id: CharacterId) -> bool {
self.characters.contains_key(&id)
}
#[allow(dead_code)]
2019-12-16 19:29:32 +00:00
pub fn get_character_by_id(&self, id: CharacterId) -> Option<&Character<'gc>> {
self.characters.get(&id)
}
#[allow(dead_code)]
2019-12-16 19:29:32 +00:00
pub fn get_character_by_export_name(&self, name: &str) -> Option<&Character<'gc>> {
self.export_characters.get(name)
}
2019-04-25 17:52:22 +00:00
pub fn instantiate_display_object(
&self,
id: CharacterId,
2019-05-24 17:25:03 +00:00
gc_context: MutationContext<'gc, '_>,
prototypes: &SystemPrototypes<'gc>,
) -> Result<DisplayObject<'gc>, Box<dyn std::error::Error>> {
let (mut obj, proto): (DisplayObject<'gc>, Object<'gc>) = match self.characters.get(&id) {
Some(Character::Bitmap(bitmap)) => (bitmap.instantiate(gc_context), prototypes.object),
Some(Character::EditText(edit_text)) => {
(edit_text.instantiate(gc_context), prototypes.object)
}
Some(Character::Graphic(graphic)) => {
(graphic.instantiate(gc_context), prototypes.object)
}
Some(Character::MorphShape(morph_shape)) => {
(morph_shape.instantiate(gc_context), prototypes.object)
}
Some(Character::MovieClip(movie_clip)) => {
(movie_clip.instantiate(gc_context), prototypes.movie_clip)
}
Some(Character::Button(button)) => (button.instantiate(gc_context), prototypes.object),
Some(Character::Text(text)) => (text.instantiate(gc_context), prototypes.object),
2019-05-07 10:22:58 +00:00
Some(_) => return Err("Not a DisplayObject".into()),
None => {
log::error!("Tried to instantiate non-registered character ID {}", id);
return Err("Character id doesn't exist".into());
}
2019-05-07 10:22:58 +00:00
};
obj.post_instantiation(gc_context, obj, proto);
Ok(obj)
2019-04-25 17:52:22 +00:00
}
2019-12-17 05:21:59 +00:00
pub fn get_font(&self, id: CharacterId) -> Option<Font<'gc>> {
if let Some(&Character::Font(font)) = self.characters.get(&id) {
2019-05-04 18:45:11 +00:00
Some(font)
} else {
None
}
}
2019-05-05 22:55:27 +00:00
pub fn get_sound(&self, id: CharacterId) -> Option<SoundHandle> {
if let Some(Character::Sound(sound)) = self.characters.get(&id) {
Some(*sound)
} else {
None
}
}
pub fn set_jpeg_tables(&mut self, data: Vec<u8>) {
if self.jpeg_tables.is_some() {
// SWF spec says there should only be one JPEGTables tag.
// TODO: What is the behavior when there are multiples?
log::warn!("SWF contains multiple JPEGTables tags");
return;
}
// Some SWFs have a JPEGTables tag with 0 length; ignore these.
// (Does this happen when there is only a single DefineBits tag?)
self.jpeg_tables = if data.is_empty() {
None
} else {
Some(crate::backend::render::remove_invalid_jpeg_data(&data[..]).to_vec())
}
}
pub fn jpeg_tables(&self) -> Option<&[u8]> {
self.jpeg_tables.as_ref().map(|data| &data[..])
}
2019-05-24 17:25:03 +00:00
/// Returns the device font for use when a font is unavailable.
2019-12-17 05:21:59 +00:00
pub fn device_font(&self) -> Option<Font<'gc>> {
self.device_font
}
/// Sets the device font.
2019-12-17 05:21:59 +00:00
pub fn set_device_font(&mut self, font: Option<Font<'gc>>) {
self.device_font = font;
2019-05-24 17:25:03 +00:00
}
}
unsafe impl<'gc> gc_arena::Collect for Library<'gc> {
#[inline]
fn trace(&self, cc: gc_arena::CollectionContext) {
for character in self.characters.values() {
character.trace(cc);
}
2019-12-17 05:21:59 +00:00
self.device_font.trace(cc);
2019-05-24 17:25:03 +00:00
}
}
impl Default for Library<'_> {
fn default() -> Self {
Self::new()
}
}