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.
This commit is contained in:
relrelb 2021-10-30 10:01:42 +03:00 committed by Mike Welsh
parent a1130b973f
commit 34d1fa5226
6 changed files with 633 additions and 329 deletions

View File

@ -1,6 +1,7 @@
use crate::avm1::activation::Activation; use crate::avm1::activation::Activation;
use crate::avm1::error::Error; use crate::avm1::error::Error;
use crate::avm1::globals::display_object; 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::property_decl::{define_properties_on, Declaration};
use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::avm1::{Object, ScriptObject, TObject, Value};
use crate::avm_error; use crate::avm_error;
@ -128,8 +129,7 @@ fn get_new_text_format<'gc>(
_args: &[Value<'gc>], _args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
let tf = text_field.new_text_format(); let tf = text_field.new_text_format();
Ok(TextFormatObject::new(activation, tf).into())
Ok(tf.as_avm1_object(activation)?.into())
} }
fn set_new_text_format<'gc>( fn set_new_text_format<'gc>(
@ -137,11 +137,12 @@ fn set_new_text_format<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, 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 { if let Value::Object(tf) = tf {
let tf_parsed = TextFormat::from_avm1_object(tf, activation)?; if let Some(tf) = tf.as_text_format_object() {
text_field.set_new_text_format(tf_parsed, &mut activation.context); text_field.set_new_text_format(tf.text_format().clone(), &mut activation.context);
}
} }
Ok(Value::Undefined) Ok(Value::Undefined)
@ -164,10 +165,8 @@ fn get_text_format<'gc>(
_ => (0, text_field.text_length()), _ => (0, text_field.text_length()),
}; };
Ok(text_field let tf = text_field.text_format(from, to);
.text_format(from, to) Ok(TextFormatObject::new(activation, tf).into())
.as_avm1_object(activation)?
.into())
} }
fn set_text_format<'gc>( fn set_text_format<'gc>(
@ -175,24 +174,24 @@ fn set_text_format<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, 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 { 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)) { text_field.set_text_format(from, to, tf.text_format().clone(), &mut activation.context);
(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);
} }
Ok(Value::Undefined) Ok(Value::Undefined)

View File

@ -1,66 +1,463 @@
//! `TextFormat` impl //! `TextFormat` impl
use crate::avm1::activation::Activation; use crate::avm1::object::text_format_object::TextFormatObject;
use crate::avm1::error::Error; use crate::avm1::property_decl::{define_properties_on, Declaration};
use crate::avm1::{Object, ScriptObject, TObject, Value}; use crate::avm1::{Activation, ArrayObject, AvmString, Error, Object, TObject, Value};
use crate::string::AvmString; use crate::avm_warn;
use crate::html::TextFormat;
use gc_arena::MutationContext; use gc_arena::MutationContext;
fn map_defined_to_string<'gc>( macro_rules! getter {
name: AvmString<'gc>, ($name:ident) => {
this: Object<'gc>, |activation, this, _args| {
activation: &mut Activation<'_, 'gc, '_>, if let Some(text_format) = this.as_text_format_object() {
val: Option<Value<'gc>>, return Ok($name(activation, &text_format.text_format()));
) -> Result<(), Error<'gc>> { }
let val = match val { Ok(Value::Undefined)
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(),
}; };
}
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(()) Ok(())
} }
fn map_defined_to_number<'gc>( fn size<'gc>(_activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> {
name: AvmString<'gc>, text_format
this: Object<'gc>, .size
.as_ref()
.map_or(Value::Null, |&size| size.into())
}
fn set_size<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
val: Option<Value<'gc>>, text_format: &mut TextFormat,
value: &Value<'gc>,
) -> Result<(), Error<'gc>> { ) -> Result<(), Error<'gc>> {
let val = match val { text_format.size = match value {
Some(Value::Undefined) => Value::Null, Value::Undefined | Value::Null => None,
Some(Value::Null) => Value::Null, // TODO: round up
None => Value::Null, value => Some(value.coerce_to_i32(activation)?.into()),
Some(v) => v.coerce_to_f64(activation)?.into(),
}; };
this.set(name, val, activation)?;
Ok(()) Ok(())
} }
fn map_defined_to_bool<'gc>( fn color<'gc>(_activation: &mut Activation<'_, 'gc, '_>, text_format: &TextFormat) -> Value<'gc> {
name: AvmString<'gc>, text_format
this: Object<'gc>, .color
.as_ref()
.map_or(Value::Null, |color| color.to_rgba().into())
}
fn set_color<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
val: Option<Value<'gc>>, text_format: &mut TextFormat,
value: &Value<'gc>,
) -> Result<(), Error<'gc>> { ) -> Result<(), Error<'gc>> {
let val = match val { text_format.color = match value {
Some(Value::Undefined) => Value::Null, Value::Undefined | Value::Null => None,
Some(Value::Null) => Value::Null, value => Some(swf::Color::from_rgba(value.coerce_to_u32(activation)?)),
None => Value::Null,
Some(v) => v.as_bool(activation.swf_version()).into(),
}; };
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<Vec<_>, 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(()) Ok(())
} }
@ -70,24 +467,75 @@ pub fn constructor<'gc>(
this: Object<'gc>, this: Object<'gc>,
args: &[Value<'gc>], args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
map_defined_to_string("font".into(), this, activation, args.get(0).cloned())?; if let Some(this) = this.as_text_format_object() {
map_defined_to_number("size".into(), this, activation, args.get(1).cloned())?; let mut text_format = TextFormat::default();
map_defined_to_number("color".into(), this, activation, args.get(2).cloned())?; set_font(
map_defined_to_bool("bold".into(), this, activation, args.get(3).cloned())?; activation,
map_defined_to_bool("italic".into(), this, activation, args.get(4).cloned())?; &mut text_format,
map_defined_to_bool("underline".into(), this, activation, args.get(5).cloned())?; args.get(0).unwrap_or(&Value::Undefined),
map_defined_to_string("url".into(), this, activation, args.get(6).cloned())?; )?;
map_defined_to_string("target".into(), this, activation, args.get(7).cloned())?; set_size(
map_defined_to_string("align".into(), this, activation, args.get(8).cloned())?; activation,
map_defined_to_number("leftMargin".into(), this, activation, args.get(9).cloned())?; &mut text_format,
map_defined_to_number( args.get(1).unwrap_or(&Value::Undefined),
"rightMargin".into(), )?;
this, set_color(
activation, activation,
args.get(10).cloned(), &mut text_format,
)?; args.get(2).unwrap_or(&Value::Undefined),
map_defined_to_number("indent".into(), this, activation, args.get(11).cloned())?; )?;
map_defined_to_number("leading".into(), this, activation, args.get(12).cloned())?; 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()) Ok(this.into())
} }
@ -96,9 +544,10 @@ pub fn constructor<'gc>(
pub fn create_proto<'gc>( pub fn create_proto<'gc>(
gc_context: MutationContext<'gc, '_>, gc_context: MutationContext<'gc, '_>,
proto: Object<'gc>, proto: Object<'gc>,
_fn_proto: Object<'gc>, fn_proto: Object<'gc>,
) -> Object<'gc> { ) -> Object<'gc> {
let tf_proto = ScriptObject::object(gc_context, Some(proto)); let text_format = TextFormatObject::empty_object(gc_context, Some(proto));
let object = text_format.as_script_object().unwrap();
tf_proto.into() define_properties_on(PROTO_DECLS, gc_context, object, fn_proto);
text_format.into()
} }

View File

@ -21,6 +21,7 @@ use crate::avm1::object::drop_shadow_filter::DropShadowFilterObject;
use crate::avm1::object::glow_filter::GlowFilterObject; use crate::avm1::object::glow_filter::GlowFilterObject;
use crate::avm1::object::gradient_bevel_filter::GradientBevelFilterObject; use crate::avm1::object::gradient_bevel_filter::GradientBevelFilterObject;
use crate::avm1::object::gradient_glow_filter::GradientGlowFilterObject; 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::transform_object::TransformObject;
use crate::avm1::object::xml_attributes_object::XmlAttributesObject; use crate::avm1::object::xml_attributes_object::XmlAttributesObject;
use crate::avm1::object::xml_idmap_object::XmlIdMapObject; use crate::avm1::object::xml_idmap_object::XmlIdMapObject;
@ -51,6 +52,7 @@ pub mod shared_object;
pub mod sound_object; pub mod sound_object;
pub mod stage_object; pub mod stage_object;
pub mod super_object; pub mod super_object;
pub mod text_format_object;
pub mod transform_object; pub mod transform_object;
pub mod value_object; pub mod value_object;
pub mod xml_attributes_object; pub mod xml_attributes_object;
@ -88,6 +90,7 @@ pub mod xml_object;
GradientGlowFilterObject(GradientGlowFilterObject<'gc>), GradientGlowFilterObject(GradientGlowFilterObject<'gc>),
DateObject(DateObject<'gc>), DateObject(DateObject<'gc>),
BitmapData(BitmapDataObject<'gc>), BitmapData(BitmapDataObject<'gc>),
TextFormatObject(TextFormatObject<'gc>),
} }
)] )]
pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy { pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy {
@ -608,6 +611,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
None None
} }
/// Get the underlying `TextFormatObject`, if it exists
fn as_text_format_object(&self) -> Option<TextFormatObject<'gc>> {
None
}
fn as_ptr(&self) -> *const ObjectPtr; fn as_ptr(&self) -> *const ObjectPtr;
/// Check if this object is in the prototype chain of the specified test object. /// Check if this object is in the prototype chain of the specified test object.

View File

@ -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<Object<'gc>>) -> 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<TextFormat> {
Ref::map(self.0.read(), |o| &o.text_format)
}
pub fn text_format_mut(&self, gc_context: MutationContext<'gc, '_>) -> RefMut<TextFormat> {
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);
});
}

View File

@ -1,10 +1,5 @@
//! Classes that store formatting options //! 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::{ use crate::avm2::{
Activation as Avm2Activation, ArrayObject as Avm2ArrayObject, Error as Avm2Error, Activation as Avm2Activation, ArrayObject as Avm2ArrayObject, Error as Avm2Error,
Namespace as Avm2Namespace, Object as Avm2Object, QName as Avm2QName, TObject as Avm2TObject, Namespace as Avm2Namespace, Object as Avm2Object, QName as Avm2QName, TObject as Avm2TObject,
@ -135,61 +130,6 @@ pub struct TextFormat {
pub target: Option<String>, pub target: Option<String>,
} }
fn getstr_from_avm1_object<'gc>(
object: Avm1Object<'gc>,
name: &'static str,
activation: &mut Avm1Activation<'_, 'gc, '_>,
) -> Result<Option<String>, 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<Option<f64>, 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<Option<bool>, 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<Option<Vec<f64>>, 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<Vec<_>, 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>( fn getstr_from_avm2_object<'gc>(
object: Avm2Object<'gc>, object: Avm2Object<'gc>,
pubname: &'static str, 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<Self, crate::avm1::error::Error<'gc>> {
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`. /// Construct a `TextFormat` from an AVM2 `TextFormat`.
pub fn from_avm2_object<'gc>( pub fn from_avm2_object<'gc>(
object2: Avm2Object<'gc>, object2: Avm2Object<'gc>,
@ -550,151 +454,6 @@ impl TextFormat {
tf tf
} }
/// Construct a `TextFormat` AVM1 object from this text format object.
pub fn as_avm1_object<'gc>(
&self,
activation: &mut Avm1Activation<'_, 'gc, '_>,
) -> Result<Avm1Object<'gc>, 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. /// Construct a `TextFormat` AVM2 object from this text format object.
pub fn as_avm2_object<'gc>( pub fn as_avm2_object<'gc>(
&self, &self,

View File

@ -424,6 +424,24 @@ impl Color {
Self { r, g, b, a: alpha } 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. /// Converts the color to a 32-bit RGB value.
/// ///
/// The alpha value does not get stored. /// The alpha value does not get stored.