Skip to content

Commit

Permalink
avm2: Implement Font.registerFont
Browse files Browse the repository at this point in the history
  • Loading branch information
Dinnerbone committed Oct 30, 2023
1 parent 9ecb7d2 commit 36eda35
Show file tree
Hide file tree
Showing 12 changed files with 416 additions and 99 deletions.
17 changes: 17 additions & 0 deletions core/src/avm2/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,23 @@ pub fn make_error_1506<'gc>(activation: &mut Activation<'_, 'gc>) -> Error<'gc>
}
}

#[inline(never)]
#[cold]
pub fn make_error_1508<'gc>(activation: &mut Activation<'_, 'gc>, param_name: &str) -> Error<'gc> {
let err = argument_error(
activation,
&format!(
"Error #1508: The value specified for argument {} is invalid.",
param_name
),
1508,
);
match err {
Ok(err) => Error::AvmError(err),
Err(err) => err,
}
}

#[inline(never)]
#[cold]
pub fn make_error_2008<'gc>(activation: &mut Activation<'_, 'gc>, param_name: &str) -> Error<'gc> {
Expand Down
31 changes: 27 additions & 4 deletions core/src/avm2/globals/flash/text/font.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! `flash.text.Font` builtin/prototype
use crate::avm2::activation::Activation;
use crate::avm2::error::make_error_1508;
use crate::avm2::object::{FontObject, Object, TObject};
use crate::avm2::parameters::ParametersExt;
use crate::avm2::value::Value;
Expand All @@ -9,6 +10,7 @@ use crate::avm2_stub_method;
use crate::string::AvmString;

pub use crate::avm2::object::font_allocator;
use crate::character::Character;
use crate::font::FontType;

/// Implements `Font.fontName`
Expand Down Expand Up @@ -95,10 +97,14 @@ pub fn enumerate_fonts<'gc>(
);
}

for font in activation.context.library.global_fonts() {
storage.push(FontObject::for_font(activation.context.gc_context, font_class, font).into());
}

if let Some(library) = activation
.context
.library
.library_for_movie(activation.context.swf.clone())
.library_for_movie(activation.caller_movie().unwrap())
{
for font in library.embedded_fonts() {
// TODO: EmbeddedCFF isn't supposed to show until it's been used (some kind of internal initialization method?)
Expand All @@ -118,8 +124,25 @@ pub fn enumerate_fonts<'gc>(
pub fn register_font<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_method!(activation, "flash.text.Font", "registerFont");
Ok(Value::Undefined)
let object = args.get_object(activation, 0, "font")?;

if let Some(class) = object.as_class_object() {
if let Some((movie, id)) = activation
.context
.library
.avm2_class_registry()
.class_symbol(class)
{
if let Some(lib) = activation.context.library.library_for_movie(movie) {
if let Some(Character::Font(font)) = lib.character_by_id(id) {
activation.context.library.register_global_font(*font);
return Ok(Value::Undefined);
}
}
}
}

Err(make_error_1508(activation, "font"))
}
11 changes: 8 additions & 3 deletions core/src/html/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,14 +463,19 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
span: &TextSpan,
is_device_font: bool,
) -> Option<Font<'gc>> {
let library = context.library.library_for_movie_mut(self.movie.clone());
let font_name = span.font.to_utf8_lossy();

// Note that the SWF can still contain a DefineFont tag with no glyphs/layout info in this case (see #451).
// In an ideal world, device fonts would search for a matching font on the system and render it in some way.
if !is_device_font {
if let Some(font) = library
.get_embedded_font_by_name(&font_name, span.bold, span.italic)
if let Some(font) = context
.library
.get_embedded_font_by_name(
&font_name,
span.bold,
span.italic,
Some(self.movie.clone()),
)
.filter(|f| f.has_glyphs())
{
return Some(font);
Expand Down
208 changes: 116 additions & 92 deletions core/src/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ pub struct MovieLibrary<'gc> {
characters: HashMap<CharacterId, Character<'gc>>,
export_characters: Avm1PropertyMap<'gc, CharacterId>,
jpeg_tables: Option<Vec<u8>>,
fonts: HashMap<FontDescriptor, Font<'gc>>,
fonts: FontMap<'gc>,
avm2_domain: Option<Avm2Domain<'gc>>,
}

Expand All @@ -136,7 +136,7 @@ impl<'gc> MovieLibrary<'gc> {
characters: HashMap::new(),
export_characters: Avm1PropertyMap::new(),
jpeg_tables: None,
fonts: HashMap::new(),
fonts: Default::default(),
avm2_domain: None,
}
}
Expand All @@ -145,10 +145,7 @@ impl<'gc> MovieLibrary<'gc> {
// TODO(Herschel): What is the behavior if id already exists?
if !self.contains_character(id) {
if let Character::Font(font) = character {
// The first font with a given descriptor wins
if !self.fonts.contains_key(font.descriptor()) {
self.fonts.insert(font.descriptor().clone(), font);
}
self.fonts.register(font);
}

self.characters.insert(id, character);
Expand Down Expand Up @@ -259,92 +256,7 @@ impl<'gc> MovieLibrary<'gc> {
}

pub fn embedded_fonts(&self) -> Vec<Font<'gc>> {
self.fonts.values().cloned().collect()
}

/// Find a font by it's name and parameters.
pub fn get_embedded_font_by_name(
&self,
name: &str,
is_bold: bool,
is_italic: bool,
) -> Option<Font<'gc>> {
// The order here is specific, and tested in `tests/swfs/fonts/embed_matching/fallback_preferences`

// Exact match
if let Some(font) = self
.fonts
.get(&FontDescriptor::from_parts(name, is_bold, is_italic))
{
return Some(*font);
}

if is_italic ^ is_bold {
// If one is set (but not both), then try upgrading to bold italic...
if let Some(font) = self
.fonts
.get(&FontDescriptor::from_parts(name, true, true))
{
return Some(*font);
}

// and then downgrading to regular
if let Some(font) = self
.fonts
.get(&FontDescriptor::from_parts(name, false, false))
{
return Some(*font);
}

// and then finally whichever one we don't have set
if let Some(font) = self
.fonts
.get(&FontDescriptor::from_parts(name, !is_bold, !is_italic))
{
return Some(*font);
}
} else {
// We don't have an exact match and we were either looking for regular or bold-italic

if is_italic && is_bold {
// Do we have regular? (unless we already looked for it)
if let Some(font) = self
.fonts
.get(&FontDescriptor::from_parts(name, false, false))
{
return Some(*font);
}
}

// Do we have bold?
if let Some(font) = self
.fonts
.get(&FontDescriptor::from_parts(name, true, false))
{
return Some(*font);
}

// Do we have italic?
if let Some(font) = self
.fonts
.get(&FontDescriptor::from_parts(name, false, true))
{
return Some(*font);
}

if !is_bold && !is_italic {
// Do we have bold italic? (unless we already looked for it)
if let Some(font) = self
.fonts
.get(&FontDescriptor::from_parts(name, true, true))
{
return Some(*font);
}
}
}

// If there's no match at all, then it should not show anything...
None
self.fonts.all()
}

/// Returns the `Graphic` with the given character ID.
Expand Down Expand Up @@ -463,6 +375,10 @@ pub struct Library<'gc> {
// TODO: Descriptors shouldn't be stored in fonts. Fonts should be a list that we iterate and ask "do you match". A font can have zero or many names.
device_fonts: FnvHashMap<String, Font<'gc>>,

/// "Global" embedded fonts, registered through AVM2 `Font.registerFont`.
/// These should be checked before any Movie-specific library's own fonts.
global_fonts: FontMap<'gc>,

/// A set of which fonts we've asked from the backend already, to help with negative caching.
/// If we've asked for a specific font, record it here and don't ask again.
font_lookup_cache: FnvHashSet<String>,
Expand All @@ -488,6 +404,7 @@ unsafe impl<'gc> gc_arena::Collect for Library<'gc> {
val.trace(cc);
}
self.device_fonts.trace(cc);
self.global_fonts.trace(cc);
self.avm2_class_registry.trace(cc);
}
}
Expand All @@ -497,6 +414,7 @@ impl<'gc> Library<'gc> {
Self {
movie_libraries: PtrWeakKeyHashMap::new(),
device_fonts: Default::default(),
global_fonts: Default::default(),
font_lookup_cache: Default::default(),
default_font_names: Default::default(),
default_font_cache: Default::default(),
Expand Down Expand Up @@ -604,6 +522,35 @@ impl<'gc> Library<'gc> {
self.default_font_cache.clear();
}

/// Find a font by it's name and parameters.
pub fn get_embedded_font_by_name(
&self,
name: &str,
is_bold: bool,
is_italic: bool,
movie: Option<Arc<SwfMovie>>,
) -> Option<Font<'gc>> {
if let Some(font) = self.global_fonts.find(name, is_bold, is_italic) {
return Some(font);
}
if let Some(movie) = movie {
if let Some(library) = self.library_for_movie(movie) {
if let Some(font) = library.fonts.find(name, is_bold, is_italic) {
return Some(font);
}
}
}
None
}

pub fn global_fonts(&self) -> Vec<Font<'gc>> {
self.global_fonts.all()
}

pub fn register_global_font(&mut self, font: Font<'gc>) {
self.global_fonts.register(font);
}

/// Get the AVM2 class registry.
pub fn avm2_class_registry(&self) -> &Avm2ClassRegistry<'gc> {
&self.avm2_class_registry
Expand All @@ -614,3 +561,80 @@ impl<'gc> Library<'gc> {
&mut self.avm2_class_registry
}
}

#[derive(Collect, Default)]
#[collect(no_drop)]
struct FontMap<'gc>(FnvHashMap<FontDescriptor, Font<'gc>>);

impl<'gc> FontMap<'gc> {
pub fn register(&mut self, font: Font<'gc>) {
// The first font with a given descriptor wins
if !self.0.contains_key(font.descriptor()) {
self.0.insert(font.descriptor().clone(), font);
}
}

pub fn find(&self, name: &str, is_bold: bool, is_italic: bool) -> Option<Font<'gc>> {
// The order here is specific, and tested in `tests/swfs/fonts/embed_matching/fallback_preferences`

// Exact match
if let Some(font) = self
.0
.get(&FontDescriptor::from_parts(name, is_bold, is_italic))
{
return Some(*font);
}

if is_italic ^ is_bold {
// If one is set (but not both), then try upgrading to bold italic...
if let Some(font) = self.0.get(&FontDescriptor::from_parts(name, true, true)) {
return Some(*font);
}

// and then downgrading to regular
if let Some(font) = self.0.get(&FontDescriptor::from_parts(name, false, false)) {
return Some(*font);
}

// and then finally whichever one we don't have set
if let Some(font) = self
.0
.get(&FontDescriptor::from_parts(name, !is_bold, !is_italic))
{
return Some(*font);
}
} else {
// We don't have an exact match and we were either looking for regular or bold-italic

if is_italic && is_bold {
// Do we have regular? (unless we already looked for it)
if let Some(font) = self.0.get(&FontDescriptor::from_parts(name, false, false)) {
return Some(*font);
}
}

// Do we have bold?
if let Some(font) = self.0.get(&FontDescriptor::from_parts(name, true, false)) {
return Some(*font);
}

// Do we have italic?
if let Some(font) = self.0.get(&FontDescriptor::from_parts(name, false, true)) {
return Some(*font);
}

if !is_bold && !is_italic {
// Do we have bold italic? (unless we already looked for it)
if let Some(font) = self.0.get(&FontDescriptor::from_parts(name, true, true)) {
return Some(*font);
}
}
}

None
}

pub fn all(&self) -> Vec<Font<'gc>> {
self.0.values().copied().collect()
}
}
Loading

0 comments on commit 36eda35

Please sign in to comment.