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:
parent
a1130b973f
commit
34d1fa5226
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue