From 34d1fa5226ccec438e787abd2e4ed3004de64d46 Mon Sep 17 00:00:00 2001 From: relrelb Date: Sat, 30 Oct 2021 10:01:42 +0300 Subject: [PATCH] avm1: Introduce `TextFormatObject` `TextFormat` objects differ from regular objects in that `TextField.setTextFormat` and `TextField.setNewTextFormat` accept only the former, and ignore the latter. Also, `TextFormat.prototype` has native accessors that coerce the values on get/set. --- core/src/avm1/globals/text_field.rs | 47 +- core/src/avm1/globals/text_format.rs | 577 ++++++++++++++++++--- core/src/avm1/object.rs | 8 + core/src/avm1/object/text_format_object.rs | 71 +++ core/src/html/text_format.rs | 241 --------- swf/src/types.rs | 18 + 6 files changed, 633 insertions(+), 329 deletions(-) create mode 100644 core/src/avm1/object/text_format_object.rs diff --git a/core/src/avm1/globals/text_field.rs b/core/src/avm1/globals/text_field.rs index b7644f3f0..46994d238 100644 --- a/core/src/avm1/globals/text_field.rs +++ b/core/src/avm1/globals/text_field.rs @@ -1,6 +1,7 @@ use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::globals::display_object; +use crate::avm1::object::text_format_object::TextFormatObject; use crate::avm1::property_decl::{define_properties_on, Declaration}; use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::avm_error; @@ -128,8 +129,7 @@ fn get_new_text_format<'gc>( _args: &[Value<'gc>], ) -> Result, Error<'gc>> { let tf = text_field.new_text_format(); - - Ok(tf.as_avm1_object(activation)?.into()) + Ok(TextFormatObject::new(activation, tf).into()) } fn set_new_text_format<'gc>( @@ -137,11 +137,12 @@ fn set_new_text_format<'gc>( activation: &mut Activation<'_, 'gc, '_>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let tf = args.get(0).cloned().unwrap_or(Value::Undefined); + let tf = args.get(0).unwrap_or(&Value::Undefined); if let Value::Object(tf) = tf { - let tf_parsed = TextFormat::from_avm1_object(tf, activation)?; - text_field.set_new_text_format(tf_parsed, &mut activation.context); + if let Some(tf) = tf.as_text_format_object() { + text_field.set_new_text_format(tf.text_format().clone(), &mut activation.context); + } } Ok(Value::Undefined) @@ -164,10 +165,8 @@ fn get_text_format<'gc>( _ => (0, text_field.text_length()), }; - Ok(text_field - .text_format(from, to) - .as_avm1_object(activation)? - .into()) + let tf = text_field.text_format(from, to); + Ok(TextFormatObject::new(activation, tf).into()) } fn set_text_format<'gc>( @@ -175,24 +174,24 @@ fn set_text_format<'gc>( activation: &mut Activation<'_, 'gc, '_>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let tf = args.last().cloned().unwrap_or(Value::Undefined); + let tf = args.last().unwrap_or(&Value::Undefined); if let Value::Object(tf) = tf { - let tf_parsed = TextFormat::from_avm1_object(tf, activation)?; + if let Some(tf) = tf.as_text_format_object() { + let (from, to) = match (args.get(0), args.get(1)) { + (Some(f), Some(t)) if args.len() > 2 => ( + f.coerce_to_f64(activation)? as usize, + t.coerce_to_f64(activation)? as usize, + ), + (Some(f), _) if args.len() > 1 => { + let v = f.coerce_to_f64(activation)? as usize; + (v, v.saturating_add(1)) + } + _ => (0, text_field.text_length()), + }; - let (from, to) = match (args.get(0), args.get(1)) { - (Some(f), Some(t)) if args.len() > 2 => ( - f.coerce_to_f64(activation)? as usize, - t.coerce_to_f64(activation)? as usize, - ), - (Some(f), _) if args.len() > 1 => { - let v = f.coerce_to_f64(activation)? as usize; - (v, v.saturating_add(1)) - } - _ => (0, text_field.text_length()), - }; - - text_field.set_text_format(from, to, tf_parsed, &mut activation.context); + text_field.set_text_format(from, to, tf.text_format().clone(), &mut activation.context); + } } Ok(Value::Undefined) diff --git a/core/src/avm1/globals/text_format.rs b/core/src/avm1/globals/text_format.rs index aa00bc979..92f5cf53d 100644 --- a/core/src/avm1/globals/text_format.rs +++ b/core/src/avm1/globals/text_format.rs @@ -1,66 +1,463 @@ //! `TextFormat` impl -use crate::avm1::activation::Activation; -use crate::avm1::error::Error; -use crate::avm1::{Object, ScriptObject, TObject, Value}; -use crate::string::AvmString; +use crate::avm1::object::text_format_object::TextFormatObject; +use crate::avm1::property_decl::{define_properties_on, Declaration}; +use crate::avm1::{Activation, ArrayObject, AvmString, Error, Object, TObject, Value}; +use crate::avm_warn; +use crate::html::TextFormat; use gc_arena::MutationContext; -fn map_defined_to_string<'gc>( - name: AvmString<'gc>, - this: Object<'gc>, - activation: &mut Activation<'_, 'gc, '_>, - val: Option>, -) -> Result<(), Error<'gc>> { - let val = match val { - Some(Value::Undefined) => Value::Null, - Some(Value::Null) => Value::Null, - None => Value::Null, - Some(v) => AvmString::new( - activation.context.gc_context, - v.coerce_to_string(activation)?.to_string(), - ) - .into(), +macro_rules! getter { + ($name:ident) => { + |activation, this, _args| { + if let Some(text_format) = this.as_text_format_object() { + return Ok($name(activation, &text_format.text_format())); + } + Ok(Value::Undefined) + } }; +} - this.set(name, val, activation)?; +macro_rules! setter { + ($name:ident) => { + |activation, this, args| { + if let Some(text_format) = this.as_text_format_object() { + let value = args.get(0).unwrap_or(&Value::Undefined); + $name( + activation, + &mut text_format.text_format_mut(activation.context.gc_context), + value, + )?; + } + Ok(Value::Undefined) + } + }; +} +const PROTO_DECLS: &[Declaration] = declare_properties! { + "font" => property(getter!(font), setter!(set_font)); + "size" => property(getter!(size), setter!(set_size)); + "color" => property(getter!(color), setter!(set_color)); + "url" => property(getter!(url), setter!(set_url)); + "target" => property(getter!(target), setter!(set_target)); + "bold" => property(getter!(bold), setter!(set_bold)); + "italic" => property(getter!(italic), setter!(set_italic)); + "underline" => property(getter!(underline), setter!(set_underline)); + "align" => property(getter!(align), setter!(set_align)); + "leftMargin" => property(getter!(left_margin), setter!(set_left_margin)); + "rightMargin" => property(getter!(right_margin), setter!(set_right_margin)); + "indent" => property(getter!(indent), setter!(set_indent)); + "leading" => property(getter!(leading), setter!(set_leading)); + "blockIndent" => property(getter!(block_indent), setter!(set_block_indent)); + "tabStops" => property(getter!(tab_stops), setter!(set_tab_stops)); + "bullet" => property(getter!(bullet), setter!(set_bullet)); + "display" => property(getter!(display), setter!(set_display)); + "kerning" => property(getter!(kerning), setter!(set_kerning)); + "letterSpacing" => property(getter!(letter_spacing), setter!(set_letter_spacing)); +}; + +fn font<'gc>(activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format.font.as_ref().map_or(Value::Null, |font| { + AvmString::new(activation.context.gc_context, font).into() + }) +} + +fn set_font<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.font = match value { + Value::Undefined | Value::Null => None, + value => Some(value.coerce_to_string(activation)?.to_string()), + }; Ok(()) } -fn map_defined_to_number<'gc>( - name: AvmString<'gc>, - this: Object<'gc>, +fn size<'gc>(_activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format + .size + .as_ref() + .map_or(Value::Null, |&size| size.into()) +} + +fn set_size<'gc>( activation: &mut Activation<'_, 'gc, '_>, - val: Option>, + text_format: &mut TextFormat, + value: &Value<'gc>, ) -> Result<(), Error<'gc>> { - let val = match val { - Some(Value::Undefined) => Value::Null, - Some(Value::Null) => Value::Null, - None => Value::Null, - Some(v) => v.coerce_to_f64(activation)?.into(), + text_format.size = match value { + Value::Undefined | Value::Null => None, + // TODO: round up + value => Some(value.coerce_to_i32(activation)?.into()), }; - - this.set(name, val, activation)?; - Ok(()) } -fn map_defined_to_bool<'gc>( - name: AvmString<'gc>, - this: Object<'gc>, +fn color<'gc>(_activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format + .color + .as_ref() + .map_or(Value::Null, |color| color.to_rgba().into()) +} + +fn set_color<'gc>( activation: &mut Activation<'_, 'gc, '_>, - val: Option>, + text_format: &mut TextFormat, + value: &Value<'gc>, ) -> Result<(), Error<'gc>> { - let val = match val { - Some(Value::Undefined) => Value::Null, - Some(Value::Null) => Value::Null, - None => Value::Null, - Some(v) => v.as_bool(activation.swf_version()).into(), + text_format.color = match value { + Value::Undefined | Value::Null => None, + value => Some(swf::Color::from_rgba(value.coerce_to_u32(activation)?)), }; + Ok(()) +} - this.set(name, val, activation)?; +fn url<'gc>(activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format.url.as_ref().map_or(Value::Null, |url| { + AvmString::new(activation.context.gc_context, url).into() + }) +} +fn set_url<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.url = match value { + Value::Undefined | Value::Null => None, + value => Some(value.coerce_to_string(activation)?.to_string()), + }; + Ok(()) +} + +fn target<'gc>(activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format.target.as_ref().map_or(Value::Null, |target| { + AvmString::new(activation.context.gc_context, target).into() + }) +} + +fn set_target<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.target = match value { + Value::Undefined | Value::Null => None, + value => Some(value.coerce_to_string(activation)?.to_string()), + }; + Ok(()) +} + +fn bold<'gc>(_activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format + .bold + .as_ref() + .map_or(Value::Null, |&bold| bold.into()) +} + +fn set_bold<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.bold = match value { + Value::Undefined | Value::Null => None, + value => Some(value.as_bool(activation.swf_version())), + }; + Ok(()) +} + +fn italic<'gc>(_activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format + .italic + .as_ref() + .map_or(Value::Null, |&italic| italic.into()) +} + +fn set_italic<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.italic = match value { + Value::Undefined | Value::Null => None, + value => Some(value.as_bool(activation.swf_version())), + }; + Ok(()) +} + +fn underline<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + text_format: &TextFormat, +) -> Value<'gc> { + text_format + .underline + .as_ref() + .map_or(Value::Null, |&underline| underline.into()) +} + +fn set_underline<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.underline = match value { + Value::Undefined | Value::Null => None, + value => Some(value.as_bool(activation.swf_version())), + }; + Ok(()) +} + +fn align<'gc>(_activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format + .align + .as_ref() + .map_or(Value::Null, |align| match align { + swf::TextAlign::Left => "left".into(), + swf::TextAlign::Center => "center".into(), + swf::TextAlign::Right => "right".into(), + swf::TextAlign::Justify => "justify".into(), + }) +} + +fn set_align<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.align = match value { + Value::Undefined | Value::Null => None, + value => match value.coerce_to_string(activation)?.to_lowercase().as_str() { + "left" => Some(swf::TextAlign::Left), + "center" => Some(swf::TextAlign::Center), + "right" => Some(swf::TextAlign::Right), + "justify" => Some(swf::TextAlign::Justify), + _ => None, + }, + }; + Ok(()) +} + +fn left_margin<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + text_format: &TextFormat, +) -> Value<'gc> { + text_format + .left_margin + .as_ref() + .map_or(Value::Null, |&left_margin| left_margin.into()) +} + +fn set_left_margin<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.left_margin = match value { + Value::Undefined | Value::Null => None, + // TODO: round up + value => Some(value.coerce_to_i32(activation)?.into()), + }; + Ok(()) +} + +fn right_margin<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + text_format: &TextFormat, +) -> Value<'gc> { + text_format + .right_margin + .as_ref() + .map_or(Value::Null, |&right_margin| right_margin.into()) +} + +fn set_right_margin<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.right_margin = match value { + Value::Undefined | Value::Null => None, + // TODO: round up + value => Some(value.coerce_to_i32(activation)?.into()), + }; + Ok(()) +} + +fn indent<'gc>(_activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format + .indent + .as_ref() + .map_or(Value::Null, |&indent| indent.into()) +} + +fn set_indent<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.indent = match value { + Value::Undefined | Value::Null => None, + // TODO: round up + value => Some(value.coerce_to_i32(activation)?.into()), + }; + Ok(()) +} + +fn leading<'gc>(_activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format + .leading + .as_ref() + .map_or(Value::Null, |&leading| leading.into()) +} + +fn set_leading<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.leading = match value { + Value::Undefined | Value::Null => None, + // TODO: round up + value => Some(value.coerce_to_i32(activation)?.into()), + }; + Ok(()) +} + +fn block_indent<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + text_format: &TextFormat, +) -> Value<'gc> { + text_format + .block_indent + .as_ref() + .map_or(Value::Null, |&block_indent| block_indent.into()) +} + +fn set_block_indent<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.block_indent = match value { + Value::Undefined | Value::Null => None, + // TODO: round up + value => Some(value.coerce_to_i32(activation)?.into()), + }; + Ok(()) +} + +fn tab_stops<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &TextFormat, +) -> Value<'gc> { + text_format + .tab_stops + .as_ref() + .map_or(Value::Null, |tab_stops| { + ArrayObject::new( + activation.context.gc_context, + activation.context.avm1.prototypes().array, + tab_stops.iter().map(|&x| x.into()), + ) + .into() + }) +} + +fn set_tab_stops<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.tab_stops = match value { + Value::Object(object) => { + let length = object.length(activation)?; + let tab_stops: Result, Error<'gc>> = (0..length) + .map(|i| { + let element = object.get_element(activation, i); + // TODO: round up + Ok(element.coerce_to_i32(activation)?.into()) + }) + .collect(); + Some(tab_stops?) + } + _ => None, + }; + Ok(()) +} + +fn bullet<'gc>(_activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format + .bullet + .as_ref() + .map_or(Value::Null, |&bullet| bullet.into()) +} + +fn set_bullet<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.bullet = match value { + Value::Undefined | Value::Null => None, + value => Some(value.as_bool(activation.swf_version())), + }; + Ok(()) +} + +fn display<'gc>(activation: &mut Activation<'_, 'gc, '_>, _text_format: &TextFormat) -> Value<'gc> { + avm_warn!(activation, "TextFormat.display: Unimplemented"); + Value::Null +} + +fn set_display<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + _text_format: &mut TextFormat, + _value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + avm_warn!(activation, "TextFormat.display: Unimplemented"); + Ok(()) +} + +fn kerning<'gc>(_activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> { + text_format + .kerning + .as_ref() + .map_or(Value::Null, |&kerning| kerning.into()) +} + +fn set_kerning<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.kerning = match value { + Value::Undefined | Value::Null => None, + value => Some(value.as_bool(activation.swf_version())), + }; + Ok(()) +} + +fn letter_spacing<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + text_format: &TextFormat, +) -> Value<'gc> { + text_format + .letter_spacing + .as_ref() + .map_or(Value::Null, |&letter_spacing| letter_spacing.into()) +} + +fn set_letter_spacing<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + text_format: &mut TextFormat, + value: &Value<'gc>, +) -> Result<(), Error<'gc>> { + text_format.letter_spacing = match value { + Value::Undefined | Value::Null => None, + value => Some(value.coerce_to_f64(activation)?), + }; Ok(()) } @@ -70,24 +467,75 @@ pub fn constructor<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - map_defined_to_string("font".into(), this, activation, args.get(0).cloned())?; - map_defined_to_number("size".into(), this, activation, args.get(1).cloned())?; - map_defined_to_number("color".into(), this, activation, args.get(2).cloned())?; - map_defined_to_bool("bold".into(), this, activation, args.get(3).cloned())?; - map_defined_to_bool("italic".into(), this, activation, args.get(4).cloned())?; - map_defined_to_bool("underline".into(), this, activation, args.get(5).cloned())?; - map_defined_to_string("url".into(), this, activation, args.get(6).cloned())?; - map_defined_to_string("target".into(), this, activation, args.get(7).cloned())?; - map_defined_to_string("align".into(), this, activation, args.get(8).cloned())?; - map_defined_to_number("leftMargin".into(), this, activation, args.get(9).cloned())?; - map_defined_to_number( - "rightMargin".into(), - this, - activation, - args.get(10).cloned(), - )?; - map_defined_to_number("indent".into(), this, activation, args.get(11).cloned())?; - map_defined_to_number("leading".into(), this, activation, args.get(12).cloned())?; + if let Some(this) = this.as_text_format_object() { + let mut text_format = TextFormat::default(); + set_font( + activation, + &mut text_format, + args.get(0).unwrap_or(&Value::Undefined), + )?; + set_size( + activation, + &mut text_format, + args.get(1).unwrap_or(&Value::Undefined), + )?; + set_color( + activation, + &mut text_format, + args.get(2).unwrap_or(&Value::Undefined), + )?; + set_bold( + activation, + &mut text_format, + args.get(3).unwrap_or(&Value::Undefined), + )?; + set_italic( + activation, + &mut text_format, + args.get(4).unwrap_or(&Value::Undefined), + )?; + set_underline( + activation, + &mut text_format, + args.get(5).unwrap_or(&Value::Undefined), + )?; + set_url( + activation, + &mut text_format, + args.get(6).unwrap_or(&Value::Undefined), + )?; + set_target( + activation, + &mut text_format, + args.get(7).unwrap_or(&Value::Undefined), + )?; + set_align( + activation, + &mut text_format, + args.get(8).unwrap_or(&Value::Undefined), + )?; + set_left_margin( + activation, + &mut text_format, + args.get(9).unwrap_or(&Value::Undefined), + )?; + set_right_margin( + activation, + &mut text_format, + args.get(10).unwrap_or(&Value::Undefined), + )?; + set_indent( + activation, + &mut text_format, + args.get(11).unwrap_or(&Value::Undefined), + )?; + set_leading( + activation, + &mut text_format, + args.get(12).unwrap_or(&Value::Undefined), + )?; + this.set_text_format(activation.context.gc_context, text_format); + } Ok(this.into()) } @@ -96,9 +544,10 @@ pub fn constructor<'gc>( pub fn create_proto<'gc>( gc_context: MutationContext<'gc, '_>, proto: Object<'gc>, - _fn_proto: Object<'gc>, + fn_proto: Object<'gc>, ) -> Object<'gc> { - let tf_proto = ScriptObject::object(gc_context, Some(proto)); - - tf_proto.into() + let text_format = TextFormatObject::empty_object(gc_context, Some(proto)); + let object = text_format.as_script_object().unwrap(); + define_properties_on(PROTO_DECLS, gc_context, object, fn_proto); + text_format.into() } diff --git a/core/src/avm1/object.rs b/core/src/avm1/object.rs index 7251cd3c7..a387dff33 100644 --- a/core/src/avm1/object.rs +++ b/core/src/avm1/object.rs @@ -21,6 +21,7 @@ use crate::avm1::object::drop_shadow_filter::DropShadowFilterObject; use crate::avm1::object::glow_filter::GlowFilterObject; use crate::avm1::object::gradient_bevel_filter::GradientBevelFilterObject; use crate::avm1::object::gradient_glow_filter::GradientGlowFilterObject; +use crate::avm1::object::text_format_object::TextFormatObject; use crate::avm1::object::transform_object::TransformObject; use crate::avm1::object::xml_attributes_object::XmlAttributesObject; use crate::avm1::object::xml_idmap_object::XmlIdMapObject; @@ -51,6 +52,7 @@ pub mod shared_object; pub mod sound_object; pub mod stage_object; pub mod super_object; +pub mod text_format_object; pub mod transform_object; pub mod value_object; pub mod xml_attributes_object; @@ -88,6 +90,7 @@ pub mod xml_object; GradientGlowFilterObject(GradientGlowFilterObject<'gc>), DateObject(DateObject<'gc>), BitmapData(BitmapDataObject<'gc>), + TextFormatObject(TextFormatObject<'gc>), } )] pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy { @@ -608,6 +611,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into> + Clone + Copy None } + /// Get the underlying `TextFormatObject`, if it exists + fn as_text_format_object(&self) -> Option> { + None + } + fn as_ptr(&self) -> *const ObjectPtr; /// Check if this object is in the prototype chain of the specified test object. diff --git a/core/src/avm1/object/text_format_object.rs b/core/src/avm1/object/text_format_object.rs new file mode 100644 index 000000000..d97f3d5bc --- /dev/null +++ b/core/src/avm1/object/text_format_object.rs @@ -0,0 +1,71 @@ +use crate::avm1::{Activation, Object, ScriptObject, TObject}; +use crate::html::TextFormat; +use crate::impl_custom_object; +use gc_arena::{Collect, GcCell, MutationContext}; +use std::cell::{Ref, RefMut}; +use std::fmt; + +#[derive(Clone, Copy, Collect)] +#[collect(no_drop)] +pub struct TextFormatObject<'gc>(GcCell<'gc, TextFormatData<'gc>>); + +#[derive(Collect)] +#[collect(no_drop)] +pub struct TextFormatData<'gc> { + /// The underlying script object. + base: ScriptObject<'gc>, + + text_format: TextFormat, +} + +impl fmt::Debug for TextFormatObject<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let this = self.0.read(); + f.debug_struct("TextFormatObject") + .field("text_format", &this.text_format) + .finish() + } +} + +impl<'gc> TextFormatObject<'gc> { + pub fn empty_object(gc_context: MutationContext<'gc, '_>, proto: Option>) -> Self { + Self(GcCell::allocate( + gc_context, + TextFormatData { + base: ScriptObject::object(gc_context, proto), + text_format: TextFormat::default(), + }, + )) + } + + pub fn new(activation: &mut Activation<'_, 'gc, '_>, text_format: TextFormat) -> Self { + Self(GcCell::allocate( + activation.context.gc_context, + TextFormatData { + base: ScriptObject::object( + activation.context.gc_context, + Some(activation.context.avm1.prototypes.text_format), + ), + text_format, + }, + )) + } + + pub fn text_format(&self) -> Ref { + Ref::map(self.0.read(), |o| &o.text_format) + } + + pub fn text_format_mut(&self, gc_context: MutationContext<'gc, '_>) -> RefMut { + RefMut::map(self.0.write(gc_context), |o| &mut o.text_format) + } + + pub fn set_text_format(&self, gc_context: MutationContext<'gc, '_>, text_format: TextFormat) { + self.0.write(gc_context).text_format = text_format + } +} + +impl<'gc> TObject<'gc> for TextFormatObject<'gc> { + impl_custom_object!(base { + bare_object(as_text_format_object -> TextFormatObject::empty_object); + }); +} diff --git a/core/src/html/text_format.rs b/core/src/html/text_format.rs index b0a7bdf5f..c1dcb7403 100644 --- a/core/src/html/text_format.rs +++ b/core/src/html/text_format.rs @@ -1,10 +1,5 @@ //! Classes that store formatting options -use crate::avm1::activation::Activation as Avm1Activation; -use crate::avm1::{ - ArrayObject as Avm1ArrayObject, Object as Avm1Object, ScriptObject as Avm1ScriptObject, - TObject as Avm1TObject, Value as Avm1Value, -}; use crate::avm2::{ Activation as Avm2Activation, ArrayObject as Avm2ArrayObject, Error as Avm2Error, Namespace as Avm2Namespace, Object as Avm2Object, QName as Avm2QName, TObject as Avm2TObject, @@ -135,61 +130,6 @@ pub struct TextFormat { pub target: Option, } -fn getstr_from_avm1_object<'gc>( - object: Avm1Object<'gc>, - name: &'static str, - activation: &mut Avm1Activation<'_, 'gc, '_>, -) -> Result, crate::avm1::error::Error<'gc>> { - Ok(match object.get(name, activation)? { - Avm1Value::Undefined => None, - Avm1Value::Null => None, - v => Some(v.coerce_to_string(activation)?.to_string()), - }) -} - -fn getfloat_from_avm1_object<'gc>( - object: Avm1Object<'gc>, - name: &'static str, - activation: &mut Avm1Activation<'_, 'gc, '_>, -) -> Result, crate::avm1::error::Error<'gc>> { - Ok(match object.get(name, activation)? { - Avm1Value::Undefined => None, - Avm1Value::Null => None, - v => Some(v.coerce_to_f64(activation)?), - }) -} - -fn getbool_from_avm1_object<'gc>( - object: Avm1Object<'gc>, - name: &'static str, - activation: &mut Avm1Activation<'_, 'gc, '_>, -) -> Result, crate::avm1::error::Error<'gc>> { - Ok(match object.get(name, activation)? { - Avm1Value::Undefined => None, - Avm1Value::Null => None, - v => Some(v.as_bool(activation.swf_version())), - }) -} - -fn getfloatarray_from_avm1_object<'gc>( - object: Avm1Object<'gc>, - name: &'static str, - activation: &mut Avm1Activation<'_, 'gc, '_>, -) -> Result>, crate::avm1::error::Error<'gc>> { - Ok(match object.get(name, activation)? { - Avm1Value::Undefined => None, - Avm1Value::Null => None, - v => { - let v = v.coerce_to_object(activation); - let length = v.length(activation)?; - let output: Result, crate::avm1::error::Error<'gc>> = (0..length) - .map(|i| v.get_element(activation, i).coerce_to_f64(activation)) - .collect(); - Some(output?) - } - }) -} - fn getstr_from_avm2_object<'gc>( object: Avm2Object<'gc>, pubname: &'static str, @@ -337,42 +277,6 @@ impl TextFormat { } } - /// Construct a `TextFormat` from a correctly-shaped AVM1 object. - pub fn from_avm1_object<'gc>( - object1: Avm1Object<'gc>, - activation: &mut Avm1Activation<'_, 'gc, '_>, - ) -> Result> { - Ok(Self { - font: getstr_from_avm1_object(object1, "font", activation)?, - size: getfloat_from_avm1_object(object1, "size", activation)?, - color: getfloat_from_avm1_object(object1, "color", activation)? - .map(|v| swf::Color::from_rgb(v as u32, 0xFF)), - align: getstr_from_avm1_object(object1, "align", activation)?.and_then(|v| { - match v.to_lowercase().as_str() { - "left" => Some(swf::TextAlign::Left), - "center" => Some(swf::TextAlign::Center), - "right" => Some(swf::TextAlign::Right), - "justify" => Some(swf::TextAlign::Justify), - _ => None, - } - }), - bold: getbool_from_avm1_object(object1, "bold", activation)?, - italic: getbool_from_avm1_object(object1, "italic", activation)?, - underline: getbool_from_avm1_object(object1, "underline", activation)?, - left_margin: getfloat_from_avm1_object(object1, "leftMargin", activation)?, - right_margin: getfloat_from_avm1_object(object1, "rightMargin", activation)?, - indent: getfloat_from_avm1_object(object1, "indent", activation)?, - block_indent: getfloat_from_avm1_object(object1, "blockIndent", activation)?, - kerning: getbool_from_avm1_object(object1, "kerning", activation)?, - leading: getfloat_from_avm1_object(object1, "leading", activation)?, - letter_spacing: getfloat_from_avm1_object(object1, "letterSpacing", activation)?, - tab_stops: getfloatarray_from_avm1_object(object1, "tabStops", activation)?, - bullet: getbool_from_avm1_object(object1, "bullet", activation)?, - url: getstr_from_avm1_object(object1, "url", activation)?, - target: getstr_from_avm1_object(object1, "target", activation)?, - }) - } - /// Construct a `TextFormat` from an AVM2 `TextFormat`. pub fn from_avm2_object<'gc>( object2: Avm2Object<'gc>, @@ -550,151 +454,6 @@ impl TextFormat { tf } - /// Construct a `TextFormat` AVM1 object from this text format object. - pub fn as_avm1_object<'gc>( - &self, - activation: &mut Avm1Activation<'_, 'gc, '_>, - ) -> Result, crate::avm1::error::Error<'gc>> { - let object = Avm1ScriptObject::object( - activation.context.gc_context, - Some(activation.context.avm1.prototypes().text_format), - ); - - object.set( - "font", - self.font - .clone() - .map(|v| AvmString::new(activation.context.gc_context, v).into()) - .unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "size", - self.size.map(|v| v.into()).unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "color", - self.color - .clone() - .map(|v| v.to_rgb().into()) - .unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "align", - self.align - .map(|v| { - AvmString::new( - activation.context.gc_context, - match v { - swf::TextAlign::Left => "left", - swf::TextAlign::Center => "center", - swf::TextAlign::Right => "right", - swf::TextAlign::Justify => "justify", - } - .to_string(), - ) - .into() - }) - .unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "bold", - self.bold.map(|v| v.into()).unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "italic", - self.italic.map(|v| v.into()).unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "underline", - self.underline.map(|v| v.into()).unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "leftMargin", - self.left_margin - .map(|v| v.into()) - .unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "rightMargin", - self.right_margin - .map(|v| v.into()) - .unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "indent", - self.indent.map(|v| v.into()).unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "blockIndent", - self.block_indent - .map(|v| v.into()) - .unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "kerning", - self.kerning.map(|v| v.into()).unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "leading", - self.leading.map(|v| v.into()).unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "letterSpacing", - self.letter_spacing - .map(|v| v.into()) - .unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "bullet", - self.bullet.map(|v| v.into()).unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "url", - self.url - .clone() - .map(|v| AvmString::new(activation.context.gc_context, v).into()) - .unwrap_or(Avm1Value::Null), - activation, - )?; - object.set( - "target", - self.target - .clone() - .map(|v| AvmString::new(activation.context.gc_context, v).into()) - .unwrap_or(Avm1Value::Null), - activation, - )?; - - let tab_stops = self - .tab_stops - .as_ref() - .map_or(Avm1Value::Null, |tab_stops| { - Avm1ArrayObject::new( - activation.context.gc_context, - activation.context.avm1.prototypes().array, - tab_stops.iter().map(|&x| x.into()), - ) - .into() - }); - object.set("tabStops", tab_stops, activation)?; - Ok(object.into()) - } - /// Construct a `TextFormat` AVM2 object from this text format object. pub fn as_avm2_object<'gc>( &self, diff --git a/swf/src/types.rs b/swf/src/types.rs index f49ed30a2..56ac084ed 100644 --- a/swf/src/types.rs +++ b/swf/src/types.rs @@ -424,6 +424,24 @@ impl Color { Self { r, g, b, a: alpha } } + /// Creates a `Color` from a 32-bit `rgba` value. + /// + /// The byte-ordering of the 32-bit `rgba` value is AARRGGBB. + /// + /// # Examples + /// + /// ```rust + /// use swf::Color; + /// + /// let red = Color::from_rgba(0xFFFF0000); + /// let green = Color::from_rgba(0xFF00FF00); + /// let blue = Color::from_rgba(0xFF0000FF); + /// ``` + pub const fn from_rgba(rgba: u32) -> Self { + let [b, g, r, a] = rgba.to_le_bytes(); + Self { r, g, b, a } + } + /// Converts the color to a 32-bit RGB value. /// /// The alpha value does not get stored.