Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

avm2: Implement StyleSheet #16303

Merged
merged 5 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/avm2/globals/flash/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
pub mod engine;
pub mod font;
pub mod static_text;
pub mod style_sheet;
pub mod text_field;
pub mod text_format;
124 changes: 110 additions & 14 deletions core/src/avm2/globals/flash/text/StyleSheet.as
Original file line number Diff line number Diff line change
@@ -1,37 +1,133 @@
package flash.text {

import __ruffle__.stub_constructor;
import __ruffle__.stub_method;
import __ruffle__.stub_getter;

public dynamic class StyleSheet {
// Shallow copies of the original style objects. Not used by Ruffle itself, just for getStyle()
private var _styles: Object = {};

public function StyleSheet() {}

public function get styleNames():Array {
stub_getter("flash.text.StyleSheet", "styleNames");
return [];
var result = [];
for (var key in _styles) {
result.push(key);
}
return result;
}

public function clear():void {
stub_method("flash.text.StyleSheet", "clear");
_styles = {};
}

public function getStyle(styleName:String):Object {
stub_method("flash.text.StyleSheet", "getStyle");
return null;
return _createShallowCopy(_styles[styleName.toLowerCase()]);
}

public function parseCSS(CSSText:String):void {
stub_method("flash.text.StyleSheet", "parseCSS");
var parsed = innerParseCss(CSSText);
if (!parsed) {
// No thrown errors, silent failure. If the whole thing doesn't parse, just ignore it all.
return;
}

for (var key in parsed) {
setStyle(key, parsed[key]);
}
}

public function setStyle(styleName:String, styleObject:Object):void {
stub_method("flash.text.StyleSheet", "setStyle");
_styles[styleName.toLowerCase()] = _createShallowCopy(styleObject);
transform(_createShallowCopy(styleObject)); // TODO: Store this in a way that Rust can access it, when we implement `TextField.stylesheet`
}

public function transform(formatObject:Object):TextFormat {
stub_method("flash.text.StyleSheet", "transform");
return null;
if (!formatObject) {
return null;
}
var result = new TextFormat();

if (formatObject.color) {
result.color = innerParseColor(formatObject.color);
}

if (formatObject.display) {
result.display = formatObject.display;
}

if (formatObject.fontFamily) {
result.font = innerParseFontFamily(formatObject.fontFamily);
}

if (formatObject.fontSize) {
var size = parseInt(formatObject.fontSize);
if (size > 0) {
result.size = size;
}
}

if (formatObject.fontStyle == "italic") {
result.italic = true;
} else if (formatObject.fontStyle == "normal") {
result.italic = false;
}

if (formatObject.fontWeight == "bold") {
result.bold = true;
} else if (formatObject.fontWeight == "normal") {
result.bold = false;
}

if (formatObject.kerning == "true") {
result.kerning = true;
} else if (formatObject.kerning == "false") {
result.kerning = false;
} else {
// Seems to always set, not just if defined
result.kerning = parseInt(formatObject.kerning);
}

if (formatObject.leading) {
result.leading = parseInt(formatObject.leading);
}

if (formatObject.letterSpacing) {
result.letterSpacing = parseFloat(formatObject.letterSpacing);
}

if (formatObject.marginLeft) {
result.leftMargin = parseFloat(formatObject.marginLeft);
}

if (formatObject.marginRight) {
result.rightMargin = parseFloat(formatObject.marginRight);
}

if (formatObject.textAlign) {
result.align = formatObject.textAlign;
}

if (formatObject.textDecoration == "underline") {
result.underline = true;
} else if (formatObject.textDecoration == "none") {
result.underline = false;
}

if (formatObject.textIndent) {
result.indent = parseInt(formatObject.textIndent);
}

return result;
}

private function _createShallowCopy(original: *): Object {
var copy = {};
for (var key in original) {
copy[key] = original[key];
}
return copy;
}

// Avoid doing potentially expensive string parsing in AS :D
private native function innerParseCss(css: String): Object;
private native function innerParseColor(color: String): Number;
private native function innerParseFontFamily(fontFamily: String): String;
}
}
108 changes: 108 additions & 0 deletions core/src/avm2/globals/flash/text/style_sheet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use crate::avm2::parameters::ParametersExt;
use crate::avm2::{Activation, Error, Object, TObject, Value};
use crate::html::{transform_dashes_to_camel_case, CssStream};
use crate::string::AvmString;
use ruffle_wstr::{WStr, WString};

pub fn inner_parse_css<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let document = args.get_string(activation, 0)?;
let result = activation
.avm2()
.classes()
.object
.construct(activation, &[])?;

if let Ok(css) = CssStream::new(&document).parse() {
for (selector, properties) in css.into_iter() {
let object = activation
.avm2()
.classes()
.object
.construct(activation, &[])?;
for (key, value) in properties.into_iter() {
object.set_public_property(
AvmString::new(activation.gc(), transform_dashes_to_camel_case(key)),
Value::String(AvmString::new(activation.gc(), value)),
activation,
)?;
}
result.set_public_property(
AvmString::new(activation.gc(), selector),
Value::Object(object),
activation,
)?;
}
}

Ok(Value::Object(result))
}

pub fn inner_parse_color<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let input = args.get_string(activation, 0)?;

if let Some(stripped) = input.strip_prefix(WStr::from_units(b"#")) {
if stripped.len() <= 6 {
if let Ok(number) = u32::from_str_radix(&stripped.to_string(), 16) {
return Ok(number.into());
}
}
}

Ok(0.into())
}

pub fn inner_parse_font_family<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
let input = args.get_string(activation, 0)?;
let mut result = WString::new();

let mut pos = 0;
while pos < input.len() {
// Skip whitespace
while input.get(pos) == Some(' ' as u16) {
pos += 1;
}

// Find the whole value
let start = pos;
while input.get(pos) != Some(',' as u16) && pos < input.len() {
pos += 1;
}

let mut value = &input[start..pos];

if pos < input.len() {
pos += 1; // move past the comma
}

// Transform some names
if value == b"mono" {
value = WStr::from_units(b"_typewriter");
} else if value == b"sans-serif" {
value = WStr::from_units(b"_sans");
} else if value == b"serif" {
value = WStr::from_units(b"_serif");
}

// Add it to the result (without any extra space)
if !value.is_empty() {
if !result.is_empty() {
result.push_char(',');
}
result.push_str(value);
}
}

Ok(Value::String(AvmString::new(activation.gc(), result)))
}
47 changes: 39 additions & 8 deletions core/src/avm2/globals/flash/text/text_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use crate::avm2::object::{ArrayObject, Object, TObject};
use crate::avm2::value::Value;
use crate::avm2::Error;
use crate::ecma_conversions::round_to_even;
use crate::html::TextDisplay;
use crate::string::{AvmString, WStr};
use crate::{avm2_stub_getter, avm2_stub_setter};

pub use crate::avm2::object::textformat_allocator as text_format_allocator;

Expand Down Expand Up @@ -185,20 +185,51 @@ pub fn set_color<'gc>(
}

pub fn get_display<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_activation: &mut Activation<'_, 'gc>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_getter!(activation, "flash.text.TextFormat", "display");
Ok("block".into())
if let Some(text_format) = this.as_text_format() {
return Ok(text_format
.display
.as_ref()
.map_or(Value::Null, |display| match display {
TextDisplay::Block => "block".into(),
TextDisplay::Inline => "inline".into(),
TextDisplay::None => "none".into(),
}));
}

Ok(Value::Undefined)
}

pub fn set_display<'gc>(
activation: &mut Activation<'_, 'gc>,
_this: Object<'gc>,
_args: &[Value<'gc>],
this: Object<'gc>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
avm2_stub_setter!(activation, "flash.text.TextFormat", "display");
if let Some(mut text_format) = this.as_text_format_mut() {
let value = args.get(0).unwrap_or(&Value::Undefined);
let value = match value {
Value::Undefined | Value::Null => {
text_format.display = None;
return Ok(Value::Undefined);
}
value => value.coerce_to_string(activation)?,
};

text_format.display = if &value == b"block" {
Some(TextDisplay::Block)
} else if &value == b"inline" {
Some(TextDisplay::Inline)
} else if &value == b"none" {
Some(TextDisplay::None)
} else {
// No error message for this, silently set it to None/null
None
};
}

Ok(Value::Undefined)
}

Expand Down
7 changes: 5 additions & 2 deletions core/src/avm2/object/textformat_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::avm2::object::script_object::ScriptObjectData;
use crate::avm2::object::{ClassObject, Object, ObjectPtr, TObject};
use crate::avm2::value::Value;
use crate::avm2::Error;
use crate::html::TextFormat;
use crate::html::{TextDisplay, TextFormat};
use core::fmt;
use gc_arena::barrier::unlock;
use gc_arena::lock::RefLock;
Expand All @@ -21,7 +21,10 @@ pub fn textformat_allocator<'gc>(
activation.gc(),
TextFormatObjectData {
base: RefLock::new(ScriptObjectData::new(class)),
text_format: Default::default(),
text_format: RefCell::new(TextFormat {
display: Some(TextDisplay::Block),
..Default::default()
}),
},
))
.into())
Expand Down
4 changes: 3 additions & 1 deletion core/src/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ mod text_format;
pub use dimensions::BoxBounds;
pub use dimensions::Position;
pub use layout::{LayoutBox, LayoutContent, LayoutMetrics};
pub use text_format::{FormatSpans, TextFormat, TextSpan};
pub use stylesheet::{transform_dashes_to_camel_case, CssStream};
pub use text_format::{FormatSpans, TextDisplay, TextFormat, TextSpan};

mod stylesheet;
#[cfg(test)]
mod test;
Loading