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::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<Value<'gc>, 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<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 {
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<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 {
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)

View File

@ -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<Value<'gc>>,
) -> 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<Value<'gc>>,
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<Value<'gc>>,
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<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(())
}
@ -70,24 +467,75 @@ pub fn constructor<'gc>(
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, 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()
}

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::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<Object<'gc>> + Clone + Copy {
@ -608,6 +611,11 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
None
}
/// Get the underlying `TextFormatObject`, if it exists
fn as_text_format_object(&self) -> Option<TextFormatObject<'gc>> {
None
}
fn as_ptr(&self) -> *const ObjectPtr;
/// 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
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<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>(
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<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`.
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<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.
pub fn as_avm2_object<'gc>(
&self,

View File

@ -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.