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) {
// First try to parse as a frame number.
call_frame = Some((clip, frame));
// TODO(moulins): remove this UTF8 conversion
} else if let Some(frame) = clip.frame_label_to_number(&frame.to_utf8_lossy()) {
} else if let Some(frame) = clip.frame_label_to_number(frame) {
// Otherwise, it's a frame label.
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>> {
if let Some(clip) = self.target_clip() {
if let Some(clip) = clip.as_movie_clip() {
if let Some(frame) =
clip.frame_label_to_number(&label.to_str_lossy(self.encoding()))
{
let label = WString::from_utf8(&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);
} else {
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) {
// First try to parse as a frame number.
call_frame = Some((clip, frame));
// TODO(moulins): remove this UTF8 conversion
} else if let Some(frame) = clip.frame_label_to_number(&frame.to_utf8_lossy()) {
} else if let Some(frame) = clip.frame_label_to_number(frame) {
// Otherwise, it's a frame label.
call_frame = Some((clip, frame as i32));
}

View File

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

View File

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

View File

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

View File

@ -174,6 +174,12 @@ macro_rules! impl_str_methods {
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`].
#[inline]
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 super::pattern::{SearchStep, Searcher};
use super::{utils, Pattern, Units, WStr};
use super::{utils, Pattern, Units, WStr, WString};
pub struct Iter<'a> {
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 {
match s.units() {
Units::Bytes(_) => true,