core: use WStrings for display object's frame and scene labels

This commit is contained in:
Moulins 2021-10-02 03:11:10 +02:00 committed by kmeisthax
parent 84b4e33036
commit 87400b829d
7 changed files with 95 additions and 58 deletions

View File

@ -765,8 +765,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
if let Ok(frame) = frame.parse().map(f64_to_wrapping_u32) { if let Ok(frame) = frame.parse().map(f64_to_wrapping_u32) {
// First try to parse as a frame number. // First try to parse as a frame number.
call_frame = Some((clip, frame)); call_frame = Some((clip, frame));
// TODO(moulins): remove this UTF8 conversion } else if let Some(frame) = clip.frame_label_to_number(frame) {
} else if let Some(frame) = clip.frame_label_to_number(&frame.to_utf8_lossy()) {
// Otherwise, it's a frame label. // Otherwise, it's a frame label.
call_frame = Some((clip, frame.into())); call_frame = Some((clip, frame.into()));
} }
@ -1407,9 +1406,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_goto_label(&mut self, label: &'_ SwfStr) -> Result<FrameControl<'gc>, Error<'gc>> { fn action_goto_label(&mut self, label: &'_ SwfStr) -> Result<FrameControl<'gc>, Error<'gc>> {
if let Some(clip) = self.target_clip() { if let Some(clip) = self.target_clip() {
if let Some(clip) = clip.as_movie_clip() { if let Some(clip) = clip.as_movie_clip() {
if let Some(frame) = let label = WString::from_utf8(&label.to_str_lossy(self.encoding()));
clip.frame_label_to_number(&label.to_str_lossy(self.encoding())) if let Some(frame) = clip.frame_label_to_number(label.borrow()) {
{
clip.goto_frame(&mut self.context, frame, true); clip.goto_frame(&mut self.context, frame, true);
} else { } else {
avm_warn!(self, "GoToLabel: Frame label '{:?}' not found", label); avm_warn!(self, "GoToLabel: Frame label '{:?}' not found", label);

View File

@ -1010,8 +1010,7 @@ pub fn goto_frame<'gc>(
if let Ok(frame) = frame.parse().map(f64_to_wrapping_i32) { if let Ok(frame) = frame.parse().map(f64_to_wrapping_i32) {
// First try to parse as a frame number. // First try to parse as a frame number.
call_frame = Some((clip, frame)); call_frame = Some((clip, frame));
// TODO(moulins): remove this UTF8 conversion } else if let Some(frame) = clip.frame_label_to_number(frame) {
} else if let Some(frame) = clip.frame_label_to_number(&frame.to_utf8_lossy()) {
// Otherwise, it's a frame label. // Otherwise, it's a frame label.
call_frame = Some((clip, frame as i32)); call_frame = Some((clip, frame as i32));
} }

View File

@ -580,15 +580,15 @@ pub fn set_type<'gc>(
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
value: Value<'gc>, value: Value<'gc>,
) -> Result<(), Error<'gc>> { ) -> Result<(), Error<'gc>> {
match value let value = value.coerce_to_string(activation)?.to_ascii_lowercase();
.coerce_to_string(activation)?
.to_ascii_lowercase() if value == b"input" {
.as_str() this.set_editable(true, &mut activation.context);
{ } else if value == b"dynamic" {
"input" => this.set_editable(true, &mut activation.context), this.set_editable(false, &mut activation.context);
"dynamic" => this.set_editable(false, &mut activation.context), } else {
value => log::warn!("Invalid TextField.type: {}", value), log::warn!("Invalid TextField.type: {}", value);
}; }
Ok(()) Ok(())
} }

View File

@ -9,7 +9,7 @@ use crate::avm2::object::{ArrayObject, Object, TObject};
use crate::avm2::value::Value; use crate::avm2::value::Value;
use crate::avm2::Error; use crate::avm2::Error;
use crate::display_object::{MovieClip, Scene, TDisplayObject}; use crate::display_object::{MovieClip, Scene, TDisplayObject};
use crate::string::AvmString; use crate::string::{AvmString, BorrowWStr, WString};
use crate::tag_utils::SwfMovie; use crate::tag_utils::SwfMovie;
use gc_arena::{GcCell, MutationContext}; use gc_arena::{GcCell, MutationContext};
use std::sync::Arc; use std::sync::Arc;
@ -111,7 +111,7 @@ pub fn current_frame_label<'gc>(
if start_frame < mc.current_frame() { if start_frame < mc.current_frame() {
None None
} else { } else {
Some(AvmString::new(activation.context.gc_context, label).into()) Some(AvmString::new_ucs2(activation.context.gc_context, label).into())
} }
}) })
.unwrap_or(Value::Null)); .unwrap_or(Value::Null));
@ -133,7 +133,7 @@ pub fn current_label<'gc>(
return Ok(mc return Ok(mc
.current_label() .current_label()
.map(|(label, _start_frame)| { .map(|(label, _start_frame)| {
AvmString::new(activation.context.gc_context, label).into() AvmString::new_ucs2(activation.context.gc_context, label).into()
}) })
.unwrap_or(Value::Null)); .unwrap_or(Value::Null));
} }
@ -160,7 +160,7 @@ fn labels_for_scene<'gc>(
let mut frame_labels = Vec::with_capacity(labels.len()); let mut frame_labels = Vec::with_capacity(labels.len());
for (name, frame) in labels { for (name, frame) in labels {
let name: Value<'gc> = AvmString::new(activation.context.gc_context, name).into(); let name: Value<'gc> = AvmString::new_ucs2(activation.context.gc_context, name).into();
let local_frame = frame - scene_start + 1; let local_frame = frame - scene_start + 1;
let args = [name, local_frame.into()]; let args = [name, local_frame.into()];
let frame_label = frame_label_class.construct(activation, &args)?; let frame_label = frame_label_class.construct(activation, &args)?;
@ -186,7 +186,7 @@ pub fn current_labels<'gc>(
.and_then(|dobj| dobj.as_movie_clip()) .and_then(|dobj| dobj.as_movie_clip())
{ {
let scene = mc.current_scene().unwrap_or_else(|| Scene { let scene = mc.current_scene().unwrap_or_else(|| Scene {
name: "".to_string(), name: WString::default(),
start: 0, start: 0,
length: mc.total_frames(), length: mc.total_frames(),
}); });
@ -207,7 +207,7 @@ pub fn current_scene<'gc>(
.and_then(|dobj| dobj.as_movie_clip()) .and_then(|dobj| dobj.as_movie_clip())
{ {
let scene = mc.current_scene().unwrap_or_else(|| Scene { let scene = mc.current_scene().unwrap_or_else(|| Scene {
name: "".to_string(), name: WString::default(),
start: 0, start: 0,
length: mc.total_frames(), length: mc.total_frames(),
}); });
@ -240,7 +240,7 @@ pub fn scenes<'gc>(
let mut mc_scenes = mc.scenes(); let mut mc_scenes = mc.scenes();
if mc.scenes().is_empty() { if mc.scenes().is_empty() {
mc_scenes.push(Scene { mc_scenes.push(Scene {
name: "".to_string(), name: WString::default(),
start: 0, start: 0,
length: mc.total_frames(), length: mc.total_frames(),
}); });
@ -364,7 +364,7 @@ pub fn goto_frame<'gc>(
let scene = match args.get(1).cloned().unwrap_or(Value::Null) { let scene = match args.get(1).cloned().unwrap_or(Value::Null) {
Value::Null => None, Value::Null => None,
v => mc v => mc
.scene_label_to_number(&v.coerce_to_string(activation)?) .scene_label_to_number(v.coerce_to_string(activation)?.borrow())
.map(|v| v.saturating_sub(1)), .map(|v| v.saturating_sub(1)),
} }
.unwrap_or(0) as u32; .unwrap_or(0) as u32;
@ -380,7 +380,7 @@ pub fn goto_frame<'gc>(
//If the user specified a scene, we need to validate that //If the user specified a scene, we need to validate that
//the requested frame exists within that scene. //the requested frame exists within that scene.
let scene = scene.coerce_to_string(activation)?; let scene = scene.coerce_to_string(activation)?;
if !mc.frame_exists_within_scene(&frame_or_label, &scene) { if !mc.frame_exists_within_scene(frame_or_label.borrow(), scene.borrow()) {
return Err(format!( return Err(format!(
"ArgumentError: Frame label {} not found in scene {}", "ArgumentError: Frame label {} not found in scene {}",
frame_or_label, scene frame_or_label, scene
@ -389,7 +389,8 @@ pub fn goto_frame<'gc>(
} }
} }
mc.frame_label_to_number(&frame_or_label).ok_or_else(|| { mc.frame_label_to_number(frame_or_label.borrow())
.ok_or_else(|| {
format!( format!(
"ArgumentError: {} is not a valid frame label.", "ArgumentError: {} is not a valid frame label.",
frame_or_label frame_or_label

View File

@ -31,7 +31,7 @@ use crate::drawing::Drawing;
use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult}; use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult};
use crate::font::Font; use crate::font::Font;
use crate::prelude::*; use crate::prelude::*;
use crate::string::AvmString; use crate::string::{AvmString, WStr, WString};
use crate::tag_utils::{self, DecodeResult, SwfMovie, SwfSlice, SwfStream}; use crate::tag_utils::{self, DecodeResult, SwfMovie, SwfSlice, SwfStream};
use crate::vminterface::{AvmObject, AvmType, Instantiator}; use crate::vminterface::{AvmObject, AvmType, Instantiator};
use gc_arena::{Collect, Gc, GcCell, MutationContext}; use gc_arena::{Collect, Gc, GcCell, MutationContext};
@ -662,7 +662,7 @@ impl<'gc> MovieClip<'gc> {
fn scene_and_frame_labels( fn scene_and_frame_labels(
self, self,
reader: &mut SwfStream<'_>, reader: &mut SwfStream<'_>,
static_data: &mut MovieClipStatic, static_data: &mut MovieClipStatic<'gc>,
) -> DecodeResult { ) -> DecodeResult {
let mut sfl_data = reader.read_define_scene_and_frame_label_data()?; let mut sfl_data = reader.read_define_scene_and_frame_label_data()?;
sfl_data sfl_data
@ -677,7 +677,7 @@ impl<'gc> MovieClip<'gc> {
.map(|fld| fld.frame_num + 1) .map(|fld| fld.frame_num + 1)
.unwrap_or_else(|| static_data.total_frames as u32 + 1); .unwrap_or_else(|| static_data.total_frames as u32 + 1);
let label = label.to_string_lossy(reader.encoding()); let label = WString::from_utf8(&label.to_string_lossy(reader.encoding()));
static_data.scene_labels.insert( static_data.scene_labels.insert(
label.clone(), label.clone(),
Scene { Scene {
@ -690,7 +690,7 @@ impl<'gc> MovieClip<'gc> {
for FrameLabelData { frame_num, label } in sfl_data.frame_labels { for FrameLabelData { frame_num, label } in sfl_data.frame_labels {
static_data.frame_labels.insert( static_data.frame_labels.insert(
label.to_string_lossy(reader.encoding()), WString::from_utf8(&label.to_string_lossy(reader.encoding())),
frame_num as u16 + 1, frame_num as u16 + 1,
); );
} }
@ -903,10 +903,10 @@ impl<'gc> MovieClip<'gc> {
} }
/// Yield the current frame label as a tuple of string and frame number. /// Yield the current frame label as a tuple of string and frame number.
pub fn current_label(self) -> Option<(String, FrameNumber)> { pub fn current_label(self) -> Option<(WString, FrameNumber)> {
let read = self.0.read(); let read = self.0.read();
let current_frame = read.current_frame(); let current_frame = read.current_frame();
let mut best: Option<(&str, FrameNumber)> = None; let mut best: Option<(&WString, FrameNumber)> = None;
for (label, frame) in read.static_data.frame_labels.iter() { for (label, frame) in read.static_data.frame_labels.iter() {
if *frame > current_frame { if *frame > current_frame {
@ -918,16 +918,20 @@ impl<'gc> MovieClip<'gc> {
} }
} }
best.map(|(s, fnum)| (s.to_string(), fnum)) best.map(|(s, fnum)| (s.clone(), fnum))
} }
/// Yield a list of labels and frame-numbers in the current scene. /// Yield a list of labels and frame-numbers in the current scene.
/// ///
/// Labels are returned sorted by frame number. /// Labels are returned sorted by frame number.
pub fn labels_in_range(self, from: FrameNumber, to: FrameNumber) -> Vec<(String, FrameNumber)> { pub fn labels_in_range(
self,
from: FrameNumber,
to: FrameNumber,
) -> Vec<(WString, FrameNumber)> {
let read = self.0.read(); let read = self.0.read();
let mut values: Vec<(String, FrameNumber)> = read let mut values: Vec<(WString, FrameNumber)> = read
.static_data .static_data
.frame_labels .frame_labels
.iter() .iter()
@ -958,24 +962,25 @@ impl<'gc> MovieClip<'gc> {
write.avm2_class = constr; write.avm2_class = constr;
} }
pub fn frame_label_to_number(self, frame_label: &str) -> Option<FrameNumber> { pub fn frame_label_to_number(self, frame_label: WStr<'_>) -> Option<FrameNumber> {
// Frame labels are case insensitive. // Frame labels are case insensitive (ASCII).
// TODO: Should be case sensitive in AVM2.
let label = frame_label.to_ascii_lowercase(); let label = frame_label.to_ascii_lowercase();
self.0.read().static_data.frame_labels.get(&label).copied() self.0.read().static_data.frame_labels.get(&label).copied()
} }
pub fn scene_label_to_number(self, scene_label: &str) -> Option<FrameNumber> { pub fn scene_label_to_number(self, scene_label: WStr<'_>) -> Option<FrameNumber> {
//TODO: Are scene labels also case insensitive? // Never used in AVM1, so always be case sensitive.
self.0 self.0
.read() .read()
.static_data .static_data
.scene_labels .scene_labels
.get(scene_label) .get(&WString::from(scene_label))
.map(|Scene { start, .. }| start) .map(|Scene { start, .. }| start)
.copied() .copied()
} }
pub fn frame_exists_within_scene(self, frame_label: &str, scene_label: &str) -> bool { pub fn frame_exists_within_scene(self, frame_label: WStr<'_>, scene_label: WStr<'_>) -> bool {
let scene = self.scene_label_to_number(scene_label); let scene = self.scene_label_to_number(scene_label);
let frame = self.frame_label_to_number(frame_label); let frame = self.frame_label_to_number(frame_label);
@ -2132,9 +2137,11 @@ impl<'gc> TInteractiveObject<'gc> for MovieClip<'gc> {
event: ClipEvent, event: ClipEvent,
) -> ClipEventResult { ) -> ClipEventResult {
let frame_name = match event { let frame_name = match event {
ClipEvent::RollOut | ClipEvent::ReleaseOutside => Some("_up"), ClipEvent::RollOut | ClipEvent::ReleaseOutside => Some(WStr::from_units(b"_up")),
ClipEvent::RollOver | ClipEvent::Release | ClipEvent::DragOut => Some("_over"), ClipEvent::RollOver | ClipEvent::Release | ClipEvent::DragOut => {
ClipEvent::Press | ClipEvent::DragOver => Some("_down"), Some(WStr::from_units(b"_over"))
}
ClipEvent::Press | ClipEvent::DragOver => Some(WStr::from_units(b"_down")),
_ => None, _ => None,
}; };
@ -3102,14 +3109,17 @@ impl<'gc, 'a> MovieClipData<'gc> {
reader: &mut SwfStream<'a>, reader: &mut SwfStream<'a>,
tag_len: usize, tag_len: usize,
cur_frame: FrameNumber, cur_frame: FrameNumber,
static_data: &mut MovieClipStatic, static_data: &mut MovieClipStatic<'gc>,
) -> DecodeResult { ) -> DecodeResult {
let frame_label = reader.read_frame_label(tag_len)?; let frame_label = reader.read_frame_label(tag_len)?;
// Frame labels are case insensitive (ASCII). let mut label = frame_label
let label = frame_label
.label .label
.to_str_lossy(reader.encoding()) .to_str_lossy(reader.encoding())
.to_ascii_lowercase(); .into_owned();
// Frame labels are case insensitive (ASCII).
label.make_ascii_lowercase();
let label = WString::from_utf8_owned(label);
if let std::collections::hash_map::Entry::Vacant(v) = static_data.frame_labels.entry(label) if let std::collections::hash_map::Entry::Vacant(v) = static_data.frame_labels.entry(label)
{ {
v.insert(cur_frame); v.insert(cur_frame);
@ -3366,7 +3376,7 @@ impl<'gc, 'a> MovieClip<'gc> {
#[derive(Clone)] #[derive(Clone)]
pub struct Scene { pub struct Scene {
pub name: String, pub name: WString,
pub start: FrameNumber, pub start: FrameNumber,
pub length: FrameNumber, pub length: FrameNumber,
} }
@ -3374,7 +3384,7 @@ pub struct Scene {
impl Default for Scene { impl Default for Scene {
fn default() -> Self { fn default() -> Self {
Scene { Scene {
name: "".to_string(), name: WString::default(),
start: 0, start: 0,
length: u16::MAX, length: u16::MAX,
} }
@ -3388,9 +3398,9 @@ impl Default for Scene {
struct MovieClipStatic<'gc> { struct MovieClipStatic<'gc> {
id: CharacterId, id: CharacterId,
swf: SwfSlice, swf: SwfSlice,
frame_labels: HashMap<String, FrameNumber>, frame_labels: HashMap<WString, FrameNumber>,
#[collect(require_static)] #[collect(require_static)]
scene_labels: HashMap<String, Scene>, scene_labels: HashMap<WString, Scene>,
#[collect(require_static)] #[collect(require_static)]
audio_stream_info: Option<swf::SoundStreamHead>, audio_stream_info: Option<swf::SoundStreamHead>,
#[collect(require_static)] #[collect(require_static)]

View File

@ -174,6 +174,12 @@ macro_rules! impl_str_methods {
crate::string::ops::WStrToUtf8::new($deref).to_utf8_lossy() crate::string::ops::WStrToUtf8::new($deref).to_utf8_lossy()
} }
/// Returns a new string with all ASCII characters mapped to their lowercase equivalent.
#[inline]
pub fn to_ascii_lowercase($self: $receiver) -> crate::string::WString {
crate::string::ops::str_to_ascii_lowercase($deref)
}
/// Analogue of [`str::find`]. /// Analogue of [`str::find`].
#[inline] #[inline]
pub fn find<$($pat_gen)* P: crate::string::Pattern<$pat_lt>>($self: $pat_self, pattern: P) -> Option<usize> { pub fn find<$($pat_gen)* P: crate::string::Pattern<$pat_lt>>($self: $pat_self, pattern: P) -> Option<usize> {

View File

@ -4,7 +4,7 @@ use std::hash::Hasher;
use std::slice::Iter as SliceIter; use std::slice::Iter as SliceIter;
use super::pattern::{SearchStep, Searcher}; use super::pattern::{SearchStep, Searcher};
use super::{utils, Pattern, Units, WStr}; use super::{utils, Pattern, Units, WStr, WString};
pub struct Iter<'a> { pub struct Iter<'a> {
inner: Units<SliceIter<'a, u8>, SliceIter<'a, u16>>, inner: Units<SliceIter<'a, u8>, SliceIter<'a, u16>>,
@ -117,6 +117,29 @@ pub fn str_hash<H: Hasher>(s: WStr<'_>, state: &mut H) {
} }
} }
fn map_latin1_chars(s: WStr<'_>, mut map: impl FnMut(u8) -> u8) -> WString {
match s.units() {
Units::Bytes(us) => {
let us: Vec<u8> = us.iter().map(|c| map(*c)).collect();
WString::from_buf(us)
}
Units::Wide(us) => {
let us: Vec<u16> = us
.iter()
.map(|c| match u8::try_from(*c) {
Ok(c) => map(c).into(),
Err(_) => *c,
})
.collect();
WString::from_buf(us)
}
}
}
pub fn str_to_ascii_lowercase(s: WStr<'_>) -> WString {
map_latin1_chars(s, |c| c.to_ascii_lowercase())
}
pub fn str_is_latin1(s: WStr<'_>) -> bool { pub fn str_is_latin1(s: WStr<'_>) -> bool {
match s.units() { match s.units() {
Units::Bytes(_) => true, Units::Bytes(_) => true,