core: Initial EditText implementation

This commit is contained in:
Mike Welsh 2019-10-07 19:27:31 -07:00
parent 782174aa75
commit de1cedb653
7 changed files with 216 additions and 6 deletions

View File

@ -1,4 +1,5 @@
pub enum Character<'gc> {
EditText(Box<crate::edit_text::EditText<'gc>>),
Graphic(Box<crate::graphic::Graphic<'gc>>),
MovieClip(Box<crate::movie_clip::MovieClip<'gc>>),
Bitmap(crate::backend::render::BitmapHandle),
@ -13,6 +14,7 @@ unsafe impl<'gc> gc_arena::Collect for Character<'gc> {
#[inline]
fn trace(&self, cc: gc_arena::CollectionContext) {
match self {
Character::EditText(c) => c.trace(cc),
Character::Graphic(c) => c.trace(cc),
Character::MovieClip(c) => c.trace(cc),
Character::Bitmap(c) => c.trace(cc),

123
core/src/edit_text.rs Normal file
View File

@ -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
}
}

View File

@ -1,37 +1,104 @@
use crate::backend::render::{RenderBackend, ShapeHandle};
use crate::prelude::*;
type Error = Box<dyn std::error::Error>;
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.
/// This depends on the DefineFont tag version.
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 {
pub fn from_swf_tag(renderer: &mut dyn RenderBackend, tag: &swf::Font) -> Result<Font, Error> {
let mut glyphs = vec![];
for glyph in &tag.glyphs {
let shape_handle = renderer.register_glyph_shape(glyph);
glyphs.push(shape_handle);
let mut code_point_to_glyph = fnv::FnvHashMap::default();
for swf_glyph in &tag.glyphs {
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 {
glyphs,
code_point_to_glyph,
/// DefineFont3 stores coordinates at 20x the scale of DefineFont1/2.
/// (SWF19 p.164)
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()
}
/// 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]
pub fn scale(&self) -> f32 {
self.scale
}
}
#[derive(Debug, Clone)]
pub struct Glyph {
pub shape: ShapeHandle,
pub advance: i16,
}

View File

@ -8,6 +8,7 @@ mod bounding_box;
mod button;
mod character;
mod color_transform;
mod edit_text;
mod events;
mod font;
mod graphic;

View File

@ -47,6 +47,7 @@ impl<'gc> Library<'gc> {
gc_context: MutationContext<'gc, '_>,
) -> Result<DisplayNode<'gc>, Box<dyn std::error::Error>> {
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::MorphShape(morph_shape)) => morph_shape.clone(),
Some(Character::MovieClip(movie_clip)) => movie_clip.clone(),

View File

@ -617,6 +617,7 @@ impl<'gc, 'a> MovieClip<'gc> {
TagCode::DefineBitsLossless2 => self.define_bits_lossless(context, reader, 2),
TagCode::DefineButton => self.define_button_1(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::DefineFont2 => self.define_font_2(context, reader),
TagCode::DefineFont3 => self.define_font_3(context, reader),
@ -938,6 +939,21 @@ impl<'gc, 'a> MovieClip<'gc> {
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]
fn define_font_1(
&mut self,

View File

@ -75,7 +75,7 @@ impl<'gc> DisplayObject<'gc> for Text<'gc> {
context.transform_stack.push(&transform);
context
.renderer
.render_shape(glyph, context.transform_stack.transform());
.render_shape(glyph.shape, context.transform_stack.transform());
context.transform_stack.pop();
transform.matrix.tx += c.advance as f32;
}