core: Add utilities for converting an AVM2 object into a `TextFormat`
This commit is contained in:
parent
50f992b9e6
commit
e10726ef1c
|
@ -1,7 +1,14 @@
|
||||||
//! Classes that store formatting options
|
//! Classes that store formatting options
|
||||||
|
|
||||||
use crate::avm1::activation::Activation;
|
use crate::avm1::activation::Activation as Avm1Activation;
|
||||||
use crate::avm1::{AvmString, Object, ScriptObject, TObject, Value};
|
use crate::avm1::{
|
||||||
|
AvmString, Object as Avm1Object, ScriptObject as Avm1ScriptObject, TObject as Avm1TObject,
|
||||||
|
Value as Avm1Value,
|
||||||
|
};
|
||||||
|
use crate::avm2::{
|
||||||
|
Activation as Avm2Activation, Error as Avm2Error, Namespace as Avm2Namespace,
|
||||||
|
Object as Avm2Object, QName as Avm2QName, TObject as Avm2TObject, Value as Avm2Value,
|
||||||
|
};
|
||||||
use crate::context::UpdateContext;
|
use crate::context::UpdateContext;
|
||||||
use crate::html::iterators::TextSpanIter;
|
use crate::html::iterators::TextSpanIter;
|
||||||
use crate::tag_utils::SwfMovie;
|
use crate::tag_utils::SwfMovie;
|
||||||
|
@ -127,49 +134,49 @@ pub struct TextFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getstr_from_avm1_object<'gc>(
|
fn getstr_from_avm1_object<'gc>(
|
||||||
object: Object<'gc>,
|
object: Avm1Object<'gc>,
|
||||||
name: &str,
|
name: &str,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Avm1Activation<'_, 'gc, '_>,
|
||||||
) -> Result<Option<String>, crate::avm1::error::Error<'gc>> {
|
) -> Result<Option<String>, crate::avm1::error::Error<'gc>> {
|
||||||
Ok(match object.get(name, activation)? {
|
Ok(match object.get(name, activation)? {
|
||||||
Value::Undefined => None,
|
Avm1Value::Undefined => None,
|
||||||
Value::Null => None,
|
Avm1Value::Null => None,
|
||||||
v => Some(v.coerce_to_string(activation)?.to_string()),
|
v => Some(v.coerce_to_string(activation)?.to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getfloat_from_avm1_object<'gc>(
|
fn getfloat_from_avm1_object<'gc>(
|
||||||
object: Object<'gc>,
|
object: Avm1Object<'gc>,
|
||||||
name: &str,
|
name: &str,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Avm1Activation<'_, 'gc, '_>,
|
||||||
) -> Result<Option<f64>, crate::avm1::error::Error<'gc>> {
|
) -> Result<Option<f64>, crate::avm1::error::Error<'gc>> {
|
||||||
Ok(match object.get(name, activation)? {
|
Ok(match object.get(name, activation)? {
|
||||||
Value::Undefined => None,
|
Avm1Value::Undefined => None,
|
||||||
Value::Null => None,
|
Avm1Value::Null => None,
|
||||||
v => Some(v.coerce_to_f64(activation)?),
|
v => Some(v.coerce_to_f64(activation)?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getbool_from_avm1_object<'gc>(
|
fn getbool_from_avm1_object<'gc>(
|
||||||
object: Object<'gc>,
|
object: Avm1Object<'gc>,
|
||||||
name: &str,
|
name: &str,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Avm1Activation<'_, 'gc, '_>,
|
||||||
) -> Result<Option<bool>, crate::avm1::error::Error<'gc>> {
|
) -> Result<Option<bool>, crate::avm1::error::Error<'gc>> {
|
||||||
Ok(match object.get(name, activation)? {
|
Ok(match object.get(name, activation)? {
|
||||||
Value::Undefined => None,
|
Avm1Value::Undefined => None,
|
||||||
Value::Null => None,
|
Avm1Value::Null => None,
|
||||||
v => Some(v.as_bool(activation.current_swf_version())),
|
v => Some(v.as_bool(activation.current_swf_version())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getfloatarray_from_avm1_object<'gc>(
|
fn getfloatarray_from_avm1_object<'gc>(
|
||||||
object: Object<'gc>,
|
object: Avm1Object<'gc>,
|
||||||
name: &str,
|
name: &str,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Avm1Activation<'_, 'gc, '_>,
|
||||||
) -> Result<Option<Vec<f64>>, crate::avm1::error::Error<'gc>> {
|
) -> Result<Option<Vec<f64>>, crate::avm1::error::Error<'gc>> {
|
||||||
Ok(match object.get(name, activation)? {
|
Ok(match object.get(name, activation)? {
|
||||||
Value::Undefined => None,
|
Avm1Value::Undefined => None,
|
||||||
Value::Null => None,
|
Avm1Value::Null => None,
|
||||||
v => {
|
v => {
|
||||||
let mut output = Vec::new();
|
let mut output = Vec::new();
|
||||||
let v = v.coerce_to_object(activation);
|
let v = v.coerce_to_object(activation);
|
||||||
|
@ -183,6 +190,100 @@ fn getfloatarray_from_avm1_object<'gc>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getstr_from_avm2_object<'gc>(
|
||||||
|
mut object: Avm2Object<'gc>,
|
||||||
|
pubname: &'static str,
|
||||||
|
activation: &mut Avm2Activation<'_, 'gc, '_>,
|
||||||
|
) -> Result<Option<String>, Avm2Error> {
|
||||||
|
Ok(
|
||||||
|
match object.get_property(
|
||||||
|
object,
|
||||||
|
&Avm2QName::new(Avm2Namespace::public(), pubname),
|
||||||
|
activation,
|
||||||
|
)? {
|
||||||
|
Avm2Value::Undefined => None,
|
||||||
|
Avm2Value::Null => None,
|
||||||
|
v => Some(v.coerce_to_string(activation)?.to_string()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getfloat_from_avm2_object<'gc>(
|
||||||
|
mut object: Avm2Object<'gc>,
|
||||||
|
pubname: &'static str,
|
||||||
|
activation: &mut Avm2Activation<'_, 'gc, '_>,
|
||||||
|
) -> Result<Option<f64>, Avm2Error> {
|
||||||
|
Ok(
|
||||||
|
match object.get_property(
|
||||||
|
object,
|
||||||
|
&Avm2QName::new(Avm2Namespace::public(), pubname),
|
||||||
|
activation,
|
||||||
|
)? {
|
||||||
|
Avm2Value::Undefined => None,
|
||||||
|
Avm2Value::Null => None,
|
||||||
|
v => Some(v.coerce_to_number(activation)?),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getbool_from_avm2_object<'gc>(
|
||||||
|
mut object: Avm2Object<'gc>,
|
||||||
|
pubname: &'static str,
|
||||||
|
activation: &mut Avm2Activation<'_, 'gc, '_>,
|
||||||
|
) -> Result<Option<bool>, Avm2Error> {
|
||||||
|
Ok(
|
||||||
|
match object.get_property(
|
||||||
|
object,
|
||||||
|
&Avm2QName::new(Avm2Namespace::public(), pubname),
|
||||||
|
activation,
|
||||||
|
)? {
|
||||||
|
Avm2Value::Undefined => None,
|
||||||
|
Avm2Value::Null => None,
|
||||||
|
v => Some(v.coerce_to_boolean()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getfloatarray_from_avm2_object<'gc>(
|
||||||
|
mut object: Avm2Object<'gc>,
|
||||||
|
pubname: &'static str,
|
||||||
|
activation: &mut Avm2Activation<'_, 'gc, '_>,
|
||||||
|
) -> Result<Option<Vec<f64>>, Avm2Error> {
|
||||||
|
Ok(
|
||||||
|
match object.get_property(
|
||||||
|
object,
|
||||||
|
&Avm2QName::new(Avm2Namespace::public(), pubname),
|
||||||
|
activation,
|
||||||
|
)? {
|
||||||
|
Avm2Value::Undefined => None,
|
||||||
|
Avm2Value::Null => None,
|
||||||
|
v => {
|
||||||
|
let mut output = Vec::new();
|
||||||
|
let mut v = v.coerce_to_object(activation)?;
|
||||||
|
let length = v.as_array_storage().map(|v| v.length());
|
||||||
|
|
||||||
|
if let Some(length) = length {
|
||||||
|
for i in 0..length {
|
||||||
|
output.push(
|
||||||
|
v.get_property(
|
||||||
|
v,
|
||||||
|
&Avm2QName::new(
|
||||||
|
Avm2Namespace::public(),
|
||||||
|
AvmString::new(activation.context.gc_context, format!("{}", i)),
|
||||||
|
),
|
||||||
|
activation,
|
||||||
|
)?
|
||||||
|
.coerce_to_number(activation)?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(output)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
impl TextFormat {
|
impl TextFormat {
|
||||||
/// Construct a `TextFormat` from an `EditText`'s SWF tag.
|
/// Construct a `TextFormat` from an `EditText`'s SWF tag.
|
||||||
///
|
///
|
||||||
|
@ -234,10 +335,10 @@ impl TextFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a `TextFormat` from an object that is
|
/// Construct a `TextFormat` from a correctly-shaped AVM1 object.
|
||||||
pub fn from_avm1_object<'gc>(
|
pub fn from_avm1_object<'gc>(
|
||||||
object1: Object<'gc>,
|
object1: Avm1Object<'gc>,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Avm1Activation<'_, 'gc, '_>,
|
||||||
) -> Result<Self, crate::avm1::error::Error<'gc>> {
|
) -> Result<Self, crate::avm1::error::Error<'gc>> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
font: getstr_from_avm1_object(object1, "font", activation)?,
|
font: getstr_from_avm1_object(object1, "font", activation)?,
|
||||||
|
@ -270,6 +371,43 @@ impl TextFormat {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a `TextFormat` from an AVM2 `TextFormat`.
|
||||||
|
pub fn from_avm2_object<'gc>(
|
||||||
|
object2: Avm2Object<'gc>,
|
||||||
|
activation: &mut Avm2Activation<'_, 'gc, '_>,
|
||||||
|
) -> Result<Self, Avm2Error> {
|
||||||
|
Ok(Self {
|
||||||
|
font: getstr_from_avm2_object(object2, "font", activation)?,
|
||||||
|
size: getfloat_from_avm2_object(object2, "size", activation)?,
|
||||||
|
color: getfloat_from_avm2_object(object2, "color", activation)?
|
||||||
|
.map(|v| swf::Color::from_rgb(v as u32, 0xFF)),
|
||||||
|
align: getstr_from_avm2_object(object2, "align", activation)?.and_then(|v| {
|
||||||
|
//TODO: AS3 adds two extra values here
|
||||||
|
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_avm2_object(object2, "bold", activation)?,
|
||||||
|
italic: getbool_from_avm2_object(object2, "italic", activation)?,
|
||||||
|
underline: getbool_from_avm2_object(object2, "underline", activation)?,
|
||||||
|
left_margin: getfloat_from_avm2_object(object2, "leftMargin", activation)?,
|
||||||
|
right_margin: getfloat_from_avm2_object(object2, "rightMargin", activation)?,
|
||||||
|
indent: getfloat_from_avm2_object(object2, "indent", activation)?,
|
||||||
|
block_indent: getfloat_from_avm2_object(object2, "blockIndent", activation)?,
|
||||||
|
kerning: getbool_from_avm2_object(object2, "kerning", activation)?,
|
||||||
|
leading: getfloat_from_avm2_object(object2, "leading", activation)?,
|
||||||
|
letter_spacing: getfloat_from_avm2_object(object2, "letterSpacing", activation)?,
|
||||||
|
tab_stops: getfloatarray_from_avm2_object(object2, "tabStops", activation)?,
|
||||||
|
bullet: getbool_from_avm2_object(object2, "bullet", activation)?,
|
||||||
|
url: getstr_from_avm2_object(object2, "url", activation)?,
|
||||||
|
target: getstr_from_avm2_object(object2, "target", activation)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract text format parameters from presentational markup.
|
/// Extract text format parameters from presentational markup.
|
||||||
///
|
///
|
||||||
/// This assumes the "legacy" HTML path that only supports a handful of
|
/// This assumes the "legacy" HTML path that only supports a handful of
|
||||||
|
@ -413,9 +551,9 @@ impl TextFormat {
|
||||||
/// Construct a `TextFormat` AVM1 object from this text format object.
|
/// Construct a `TextFormat` AVM1 object from this text format object.
|
||||||
pub fn as_avm1_object<'gc>(
|
pub fn as_avm1_object<'gc>(
|
||||||
&self,
|
&self,
|
||||||
activation: &mut Activation<'_, 'gc, '_>,
|
activation: &mut Avm1Activation<'_, 'gc, '_>,
|
||||||
) -> Result<Object<'gc>, crate::avm1::error::Error<'gc>> {
|
) -> Result<Avm1Object<'gc>, crate::avm1::error::Error<'gc>> {
|
||||||
let object = ScriptObject::object(
|
let object = Avm1ScriptObject::object(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes().text_format),
|
Some(activation.context.avm1.prototypes().text_format),
|
||||||
);
|
);
|
||||||
|
@ -425,12 +563,12 @@ impl TextFormat {
|
||||||
self.font
|
self.font
|
||||||
.clone()
|
.clone()
|
||||||
.map(|v| AvmString::new(activation.context.gc_context, v).into())
|
.map(|v| AvmString::new(activation.context.gc_context, v).into())
|
||||||
.unwrap_or(Value::Null),
|
.unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"size",
|
"size",
|
||||||
self.size.map(|v| v.into()).unwrap_or(Value::Null),
|
self.size.map(|v| v.into()).unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
|
@ -438,7 +576,7 @@ impl TextFormat {
|
||||||
self.color
|
self.color
|
||||||
.clone()
|
.clone()
|
||||||
.map(|v| (((v.r as u32) << 16) + ((v.g as u32) << 8) + v.b as u32).into())
|
.map(|v| (((v.r as u32) << 16) + ((v.g as u32) << 8) + v.b as u32).into())
|
||||||
.unwrap_or(Value::Null),
|
.unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
|
@ -457,62 +595,70 @@ impl TextFormat {
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
.unwrap_or(Value::Null),
|
.unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"bold",
|
"bold",
|
||||||
self.bold.map(|v| v.into()).unwrap_or(Value::Null),
|
self.bold.map(|v| v.into()).unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"italic",
|
"italic",
|
||||||
self.italic.map(|v| v.into()).unwrap_or(Value::Null),
|
self.italic.map(|v| v.into()).unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"underline",
|
"underline",
|
||||||
self.underline.map(|v| v.into()).unwrap_or(Value::Null),
|
self.underline.map(|v| v.into()).unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"leftMargin",
|
"leftMargin",
|
||||||
self.left_margin.map(|v| v.into()).unwrap_or(Value::Null),
|
self.left_margin
|
||||||
|
.map(|v| v.into())
|
||||||
|
.unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"rightMargin",
|
"rightMargin",
|
||||||
self.right_margin.map(|v| v.into()).unwrap_or(Value::Null),
|
self.right_margin
|
||||||
|
.map(|v| v.into())
|
||||||
|
.unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"indent",
|
"indent",
|
||||||
self.indent.map(|v| v.into()).unwrap_or(Value::Null),
|
self.indent.map(|v| v.into()).unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"blockIndent",
|
"blockIndent",
|
||||||
self.block_indent.map(|v| v.into()).unwrap_or(Value::Null),
|
self.block_indent
|
||||||
|
.map(|v| v.into())
|
||||||
|
.unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"kerning",
|
"kerning",
|
||||||
self.kerning.map(|v| v.into()).unwrap_or(Value::Null),
|
self.kerning.map(|v| v.into()).unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"leading",
|
"leading",
|
||||||
self.leading.map(|v| v.into()).unwrap_or(Value::Null),
|
self.leading.map(|v| v.into()).unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"letterSpacing",
|
"letterSpacing",
|
||||||
self.letter_spacing.map(|v| v.into()).unwrap_or(Value::Null),
|
self.letter_spacing
|
||||||
|
.map(|v| v.into())
|
||||||
|
.unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
"bullet",
|
"bullet",
|
||||||
self.bullet.map(|v| v.into()).unwrap_or(Value::Null),
|
self.bullet.map(|v| v.into()).unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
|
@ -520,7 +666,7 @@ impl TextFormat {
|
||||||
self.url
|
self.url
|
||||||
.clone()
|
.clone()
|
||||||
.map(|v| AvmString::new(activation.context.gc_context, v).into())
|
.map(|v| AvmString::new(activation.context.gc_context, v).into())
|
||||||
.unwrap_or(Value::Null),
|
.unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
object.set(
|
object.set(
|
||||||
|
@ -528,12 +674,12 @@ impl TextFormat {
|
||||||
self.target
|
self.target
|
||||||
.clone()
|
.clone()
|
||||||
.map(|v| AvmString::new(activation.context.gc_context, v).into())
|
.map(|v| AvmString::new(activation.context.gc_context, v).into())
|
||||||
.unwrap_or(Value::Null),
|
.unwrap_or(Avm1Value::Null),
|
||||||
activation,
|
activation,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(ts) = &self.tab_stops {
|
if let Some(ts) = &self.tab_stops {
|
||||||
let tab_stops = ScriptObject::array(
|
let tab_stops = Avm1ScriptObject::array(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Some(activation.context.avm1.prototypes().array),
|
Some(activation.context.avm1.prototypes().array),
|
||||||
);
|
);
|
||||||
|
@ -546,7 +692,7 @@ impl TextFormat {
|
||||||
|
|
||||||
object.set("tabStops", tab_stops.into(), activation)?;
|
object.set("tabStops", tab_stops.into(), activation)?;
|
||||||
} else {
|
} else {
|
||||||
object.set("tabStops", Value::Null, activation)?;
|
object.set("tabStops", Avm1Value::Null, activation)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(object.into())
|
Ok(object.into())
|
||||||
|
|
Loading…
Reference in New Issue