core: Initial EditText implementation
This commit is contained in:
parent
782174aa75
commit
de1cedb653
|
@ -1,4 +1,5 @@
|
||||||
pub enum Character<'gc> {
|
pub enum Character<'gc> {
|
||||||
|
EditText(Box<crate::edit_text::EditText<'gc>>),
|
||||||
Graphic(Box<crate::graphic::Graphic<'gc>>),
|
Graphic(Box<crate::graphic::Graphic<'gc>>),
|
||||||
MovieClip(Box<crate::movie_clip::MovieClip<'gc>>),
|
MovieClip(Box<crate::movie_clip::MovieClip<'gc>>),
|
||||||
Bitmap(crate::backend::render::BitmapHandle),
|
Bitmap(crate::backend::render::BitmapHandle),
|
||||||
|
@ -13,6 +14,7 @@ unsafe impl<'gc> gc_arena::Collect for Character<'gc> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn trace(&self, cc: gc_arena::CollectionContext) {
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
||||||
match self {
|
match self {
|
||||||
|
Character::EditText(c) => c.trace(cc),
|
||||||
Character::Graphic(c) => c.trace(cc),
|
Character::Graphic(c) => c.trace(cc),
|
||||||
Character::MovieClip(c) => c.trace(cc),
|
Character::MovieClip(c) => c.trace(cc),
|
||||||
Character::Bitmap(c) => c.trace(cc),
|
Character::Bitmap(c) => c.trace(cc),
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
//! `EditText` display object and support code.
|
||||||
|
use crate::display_object::{DisplayObject, DisplayObjectBase};
|
||||||
|
use crate::player::{RenderContext, UpdateContext};
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::transform::Transform;
|
||||||
|
|
||||||
|
/// A dynamic text field.
|
||||||
|
/// The text in this text field can be changed dynamically.
|
||||||
|
/// It may be selectable or editable by the user, depending on the text field properties.
|
||||||
|
///
|
||||||
|
/// In the Flash IDE, this is created by changing the text field type to "Dynamic".
|
||||||
|
/// In AS2, this is created using `MovieClip.createTextField`.
|
||||||
|
/// In AS3, this is created with the `TextField` class. (https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/text/TextField.html)
|
||||||
|
///
|
||||||
|
/// (SWF19 DefineEditText pp. 171-174)
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct EditText<'gc> {
|
||||||
|
/// DisplayObject common properties.
|
||||||
|
base: DisplayObjectBase<'gc>,
|
||||||
|
|
||||||
|
/// Static data shared among all instances of this `EditText`.
|
||||||
|
static_data: gc_arena::Gc<'gc, EditTextStatic>,
|
||||||
|
|
||||||
|
/// The current text displayed by this text field.
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> EditText<'gc> {
|
||||||
|
/// Creates a new `EditText` from an SWF `DefineEditText` tag.
|
||||||
|
pub fn from_swf_tag(context: &mut UpdateContext<'_, 'gc, '_>, swf_tag: swf::EditText) -> Self {
|
||||||
|
Self {
|
||||||
|
base: Default::default(),
|
||||||
|
text: swf_tag.initial_text.clone().unwrap_or_default(),
|
||||||
|
static_data: gc_arena::Gc::allocate(context.gc_context, EditTextStatic(swf_tag)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'gc> DisplayObject<'gc> for EditText<'gc> {
|
||||||
|
impl_display_object!(base);
|
||||||
|
|
||||||
|
fn id(&self) -> CharacterId {
|
||||||
|
self.static_data.0.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_frame(&mut self, _context: &mut UpdateContext) {
|
||||||
|
// Noop
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&self, context: &mut RenderContext) {
|
||||||
|
// TODO: This is a stub implementation to just get some dynamic text rendering.
|
||||||
|
context.transform_stack.push(self.transform());
|
||||||
|
let static_data = &self.static_data.0;
|
||||||
|
let font_id = static_data.font_id.unwrap_or(0);
|
||||||
|
// TODO: Many of these properties should change be instance members instead
|
||||||
|
// of static data, because they can be altered via ActionScript.
|
||||||
|
let color = static_data.color.as_ref().unwrap_or_else(|| &swf::Color {
|
||||||
|
r: 0,
|
||||||
|
g: 0,
|
||||||
|
b: 0,
|
||||||
|
a: 255,
|
||||||
|
});
|
||||||
|
let mut transform: Transform = Default::default();
|
||||||
|
transform.color_transform.r_mult = f32::from(color.r) / 255.0;
|
||||||
|
transform.color_transform.g_mult = f32::from(color.g) / 255.0;
|
||||||
|
transform.color_transform.b_mult = f32::from(color.b) / 255.0;
|
||||||
|
transform.color_transform.a_mult = f32::from(color.a) / 255.0;
|
||||||
|
if let Some(font) = context.library.get_font(font_id) {
|
||||||
|
let scale = if let Some(height) = static_data.height {
|
||||||
|
transform.matrix.ty += f32::from(height);
|
||||||
|
f32::from(height) / font.scale()
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
if let Some(layout) = &static_data.layout {
|
||||||
|
transform.matrix.ty -= layout.leading.get() as f32;
|
||||||
|
}
|
||||||
|
transform.matrix.a = scale;
|
||||||
|
transform.matrix.d = scale;
|
||||||
|
let mut chars = self.text.chars().peekable();
|
||||||
|
let has_kerning_info = font.has_kerning_info();
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
if let Some(glyph) = font.get_glyph_for_char(c) {
|
||||||
|
context.transform_stack.push(&transform);
|
||||||
|
context
|
||||||
|
.renderer
|
||||||
|
.render_shape(glyph.shape, context.transform_stack.transform());
|
||||||
|
context.transform_stack.pop();
|
||||||
|
// Step horizontally.
|
||||||
|
let mut advance = f32::from(glyph.advance);
|
||||||
|
if has_kerning_info {
|
||||||
|
advance += font
|
||||||
|
.get_kerning_offset(c, chars.peek().cloned().unwrap_or('\0'))
|
||||||
|
.get() as f32;
|
||||||
|
}
|
||||||
|
transform.matrix.tx += advance * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// context.transform_stack.pop();
|
||||||
|
context.transform_stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<'gc> gc_arena::Collect for EditText<'gc> {
|
||||||
|
#[inline]
|
||||||
|
fn trace(&self, cc: gc_arena::CollectionContext) {
|
||||||
|
self.base.trace(cc);
|
||||||
|
self.static_data.trace(cc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Static data shared between all instances of a text object.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct EditTextStatic(swf::EditText);
|
||||||
|
|
||||||
|
unsafe impl<'gc> gc_arena::Collect for EditTextStatic {
|
||||||
|
#[inline]
|
||||||
|
fn needs_trace() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +1,104 @@
|
||||||
use crate::backend::render::{RenderBackend, ShapeHandle};
|
use crate::backend::render::{RenderBackend, ShapeHandle};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
pub struct Font {
|
pub struct Font {
|
||||||
glyphs: Vec<ShapeHandle>,
|
/// The list of glyphs defined in the font.
|
||||||
|
/// Used directly by `DefineText` tags.
|
||||||
|
glyphs: Vec<Glyph>,
|
||||||
|
|
||||||
|
/// A map from a Unicode code point to glyph in the `glyphs` array.
|
||||||
|
/// Used by `DefineEditText` tags.
|
||||||
|
code_point_to_glyph: fnv::FnvHashMap<u16, usize>,
|
||||||
|
|
||||||
/// The scaling applied to the font height to render at the proper size.
|
/// The scaling applied to the font height to render at the proper size.
|
||||||
/// This depends on the DefineFont tag version.
|
/// This depends on the DefineFont tag version.
|
||||||
scale: f32,
|
scale: f32,
|
||||||
|
|
||||||
|
/// Kerning infomration.
|
||||||
|
/// Maps from a pair of unicode code points to horizontal offset value.
|
||||||
|
kerning_pairs: fnv::FnvHashMap<(u16, u16), Twips>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Font {
|
impl Font {
|
||||||
pub fn from_swf_tag(renderer: &mut dyn RenderBackend, tag: &swf::Font) -> Result<Font, Error> {
|
pub fn from_swf_tag(renderer: &mut dyn RenderBackend, tag: &swf::Font) -> Result<Font, Error> {
|
||||||
let mut glyphs = vec![];
|
let mut glyphs = vec![];
|
||||||
for glyph in &tag.glyphs {
|
let mut code_point_to_glyph = fnv::FnvHashMap::default();
|
||||||
let shape_handle = renderer.register_glyph_shape(glyph);
|
for swf_glyph in &tag.glyphs {
|
||||||
glyphs.push(shape_handle);
|
let glyph = Glyph {
|
||||||
|
shape: renderer.register_glyph_shape(swf_glyph),
|
||||||
|
advance: swf_glyph.advance.unwrap_or(0),
|
||||||
|
};
|
||||||
|
let index = glyphs.len();
|
||||||
|
glyphs.push(glyph);
|
||||||
|
code_point_to_glyph.insert(swf_glyph.code, index);
|
||||||
}
|
}
|
||||||
|
let kerning_pairs: fnv::FnvHashMap<(u16, u16), Twips> = if let Some(layout) = &tag.layout {
|
||||||
|
layout
|
||||||
|
.kerning
|
||||||
|
.iter()
|
||||||
|
.map(|kerning| ((kerning.left_code, kerning.right_code), kerning.adjustment))
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
fnv::FnvHashMap::default()
|
||||||
|
};
|
||||||
Ok(Font {
|
Ok(Font {
|
||||||
glyphs,
|
glyphs,
|
||||||
|
code_point_to_glyph,
|
||||||
|
|
||||||
/// DefineFont3 stores coordinates at 20x the scale of DefineFont1/2.
|
/// DefineFont3 stores coordinates at 20x the scale of DefineFont1/2.
|
||||||
/// (SWF19 p.164)
|
/// (SWF19 p.164)
|
||||||
scale: if tag.version >= 3 { 20480.0 } else { 1024.0 },
|
scale: if tag.version >= 3 { 20480.0 } else { 1024.0 },
|
||||||
|
kerning_pairs,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_glyph(&self, i: usize) -> Option<ShapeHandle> {
|
/// Returns a glyph entry by index.
|
||||||
|
/// Used by `Text` display objects.
|
||||||
|
pub fn get_glyph(&self, i: usize) -> Option<Glyph> {
|
||||||
self.glyphs.get(i).cloned()
|
self.glyphs.get(i).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a glyph entry by character.
|
||||||
|
/// Used by `EditText` display objects.
|
||||||
|
pub fn get_glyph_for_char(&self, c: char) -> Option<Glyph> {
|
||||||
|
// TODO: Properly handle UTF-16/out-of-bounds code points.
|
||||||
|
let code_point = c as u16;
|
||||||
|
if let Some(index) = self.code_point_to_glyph.get(&code_point) {
|
||||||
|
self.get_glyph(*index)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a pair of characters, applies the offset that should be applied
|
||||||
|
/// to the advance value between these two characters.
|
||||||
|
/// Returns 0 twips if no kerning offset exists between these two characters.
|
||||||
|
pub fn get_kerning_offset(&self, left: char, right: char) -> Twips {
|
||||||
|
// TODO: Properly handle UTF-16/out-of-bounds code points.
|
||||||
|
let left_code_point = left as u16;
|
||||||
|
let right_code_point = right as u16;
|
||||||
|
self.kerning_pairs
|
||||||
|
.get(&(left_code_point, right_code_point))
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this font contains kerning information.
|
||||||
|
#[inline]
|
||||||
|
pub fn has_kerning_info(&self) -> bool {
|
||||||
|
!self.kerning_pairs.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn scale(&self) -> f32 {
|
pub fn scale(&self) -> f32 {
|
||||||
self.scale
|
self.scale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Glyph {
|
||||||
|
pub shape: ShapeHandle,
|
||||||
|
pub advance: i16,
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ mod bounding_box;
|
||||||
mod button;
|
mod button;
|
||||||
mod character;
|
mod character;
|
||||||
mod color_transform;
|
mod color_transform;
|
||||||
|
mod edit_text;
|
||||||
mod events;
|
mod events;
|
||||||
mod font;
|
mod font;
|
||||||
mod graphic;
|
mod graphic;
|
||||||
|
|
|
@ -47,6 +47,7 @@ impl<'gc> Library<'gc> {
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
) -> Result<DisplayNode<'gc>, Box<dyn std::error::Error>> {
|
) -> Result<DisplayNode<'gc>, Box<dyn std::error::Error>> {
|
||||||
let obj: Box<dyn DisplayObject<'gc>> = match self.characters.get(&id) {
|
let obj: Box<dyn DisplayObject<'gc>> = match self.characters.get(&id) {
|
||||||
|
Some(Character::EditText(edit_text)) => edit_text.clone(),
|
||||||
Some(Character::Graphic(graphic)) => graphic.clone(),
|
Some(Character::Graphic(graphic)) => graphic.clone(),
|
||||||
Some(Character::MorphShape(morph_shape)) => morph_shape.clone(),
|
Some(Character::MorphShape(morph_shape)) => morph_shape.clone(),
|
||||||
Some(Character::MovieClip(movie_clip)) => movie_clip.clone(),
|
Some(Character::MovieClip(movie_clip)) => movie_clip.clone(),
|
||||||
|
|
|
@ -617,6 +617,7 @@ impl<'gc, 'a> MovieClip<'gc> {
|
||||||
TagCode::DefineBitsLossless2 => self.define_bits_lossless(context, reader, 2),
|
TagCode::DefineBitsLossless2 => self.define_bits_lossless(context, reader, 2),
|
||||||
TagCode::DefineButton => self.define_button_1(context, reader),
|
TagCode::DefineButton => self.define_button_1(context, reader),
|
||||||
TagCode::DefineButton2 => self.define_button_2(context, reader),
|
TagCode::DefineButton2 => self.define_button_2(context, reader),
|
||||||
|
TagCode::DefineEditText => self.define_edit_text(context, reader),
|
||||||
TagCode::DefineFont => self.define_font_1(context, reader),
|
TagCode::DefineFont => self.define_font_1(context, reader),
|
||||||
TagCode::DefineFont2 => self.define_font_2(context, reader),
|
TagCode::DefineFont2 => self.define_font_2(context, reader),
|
||||||
TagCode::DefineFont3 => self.define_font_3(context, reader),
|
TagCode::DefineFont3 => self.define_font_3(context, reader),
|
||||||
|
@ -938,6 +939,21 @@ impl<'gc, 'a> MovieClip<'gc> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines a dynamic text field character.
|
||||||
|
#[inline]
|
||||||
|
fn define_edit_text(
|
||||||
|
&mut self,
|
||||||
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
|
reader: &mut SwfStream<&'a [u8]>,
|
||||||
|
) -> DecodeResult {
|
||||||
|
let swf_edit_text = reader.read_define_edit_text()?;
|
||||||
|
let edit_text = crate::edit_text::EditText::from_swf_tag(context, swf_edit_text);
|
||||||
|
context
|
||||||
|
.library
|
||||||
|
.register_character(edit_text.id(), Character::EditText(Box::new(edit_text)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn define_font_1(
|
fn define_font_1(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -75,7 +75,7 @@ impl<'gc> DisplayObject<'gc> for Text<'gc> {
|
||||||
context.transform_stack.push(&transform);
|
context.transform_stack.push(&transform);
|
||||||
context
|
context
|
||||||
.renderer
|
.renderer
|
||||||
.render_shape(glyph, context.transform_stack.transform());
|
.render_shape(glyph.shape, context.transform_stack.transform());
|
||||||
context.transform_stack.pop();
|
context.transform_stack.pop();
|
||||||
transform.matrix.tx += c.advance as f32;
|
transform.matrix.tx += c.advance as f32;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue