Impl `textWidth` / `textHeight`, although it currently only works well for single-line scenarios.

This commit is contained in:
David Wendt 2020-01-19 17:19:58 -05:00
parent 2181f0d0d0
commit 81b7958090
2 changed files with 162 additions and 53 deletions

View File

@ -72,6 +72,42 @@ macro_rules! with_text_field {
}};
}
pub fn text_width<'gc>(
_avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(etext) = this
.as_display_object()
.and_then(|dobj| dobj.as_edit_text())
{
let metrics = etext.measure_text(context);
return Ok(metrics.0.into());
}
Ok(Value::Undefined.into())
}
pub fn text_height<'gc>(
_avm: &mut Avm1<'gc>,
context: &mut UpdateContext<'_, 'gc, '_>,
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<ReturnValue<'gc>, Error> {
if let Some(etext) = this
.as_display_object()
.and_then(|dobj| dobj.as_edit_text())
{
let metrics = etext.measure_text(context);
return Ok(metrics.1.into());
}
Ok(Value::Undefined.into())
}
pub fn create_proto<'gc>(
gc_context: MutationContext<'gc, '_>,
proto: Object<'gc>,
@ -103,6 +139,21 @@ pub fn create_proto<'gc>(
}
);
object.add_property(
gc_context,
"textWidth",
Executable::Native(text_width),
None,
ReadOnly.into(),
);
object.add_property(
gc_context,
"textHeight",
Executable::Native(text_height),
None,
ReadOnly.into(),
);
object.into()
}

View File

@ -3,6 +3,7 @@ use crate::avm1::globals::text_field::attach_virtual_properties;
use crate::avm1::{Avm1, Object, ScriptObject, StageObject, TObject, Value};
use crate::context::{RenderContext, UpdateContext};
use crate::display_object::{DisplayObjectBase, TDisplayObject};
use crate::font::{Font, Glyph};
use crate::prelude::*;
use crate::transform::Transform;
use gc_arena::{Collect, Gc, GcCell, MutationContext};
@ -386,6 +387,103 @@ impl<'gc> EditText<'gc> {
pub fn set_new_text_format(self, tf: TextFormat, gc_context: MutationContext<'gc, '_>) {
self.0.write(gc_context).new_format = tf;
}
/// Measure the width and height of the `EditText`'s current text load.
///
/// The returned tuple should be interpreted as width, then height.
pub fn measure_text(self, context: &mut UpdateContext<'_, 'gc, '_>) -> (f32, f32) {
let edit_text = self.0.read();
// TODO: This is a stub implementation to just get some dynamic text rendering.
let static_data = &edit_text.static_data.0;
let font_id = static_data.font_id.unwrap_or(0);
let mut size = (0.0, 0.0);
// If the font can't be found or has no glyph information, use the "device font" instead.
// We're cheating a bit and not actually rendering text using the OS/web.
// Instead, we embed an SWF version of Noto Sans to use as the "device font", and render
// it the same as any other SWF outline text.
if let Some(font) = context
.library
.get_font(font_id)
.filter(|font| font.has_glyphs())
.or_else(|| context.library.device_font())
{
self.evaluate_font(font, &edit_text.text, |transform, glyph: &Glyph| {
//This is actually terrible matrix math.
let tx = transform.matrix.tx * transform.matrix.a;
let ty = transform.matrix.ty * transform.matrix.d;
size.0 = f32::max(size.0, tx + (glyph.advance as f32));
size.1 = f32::max(size.1, ty);
});
}
size
}
/// Evaluate an SWF font on a glyph-by-glyph basis.
///
/// This function takes a font and a text string to evaluate, as well as a
/// `glyph_func`. The given `glyph_func` will be called once for each glyph
/// that the font expects to be rendered. It's arguments will be the
/// glyph's transform, and then the glyph itself.
fn evaluate_font<FGlyph>(self, font: Font<'gc>, text: &str, mut glyph_func: FGlyph)
where
FGlyph: FnMut(&Transform, &Glyph),
{
let edit_text = self.0.read();
// TODO: This is a stub implementation to just get some dynamic text rendering.
let static_data = &edit_text.static_data.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;
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 = text.chars().peekable();
let has_kerning_info = font.has_kerning_info();
while let Some(c) = chars.next() {
// TODO: SWF text fields can contain a limited subset of HTML (and often do in SWF versions >6).
// This is a quicky-and-dirty way to skip the HTML tags. This is obviously not correct
// and we will need to properly parse and handle the HTML at some point.
// See SWF19 pp. 173-174 for supported HTML tags.
if edit_text.static_data.0.is_html && c == '<' {
// Skip characters until we see a close bracket.
chars.by_ref().skip_while(|&x| x != '>').next();
} else if let Some(glyph) = font.get_glyph_for_char(c) {
glyph_func(&transform, &glyph);
// 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;
}
}
}
}
impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
@ -428,25 +526,14 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
.unwrap_or(Value::Undefined)
}
fn render(&self, context: &mut RenderContext) {
fn render(&self, context: &mut RenderContext<'_, 'gc>) {
context.transform_stack.push(&*self.transform());
let edit_text = self.0.read();
// TODO: This is a stub implementation to just get some dynamic text rendering.
context.transform_stack.push(&*self.transform());
let static_data = &edit_text.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 the font can't be found or has no glyph information, use the "device font" instead.
// We're cheating a bit and not actually rendering text using the OS/web.
// Instead, we embed an SWF version of Noto Sans to use as the "device font", and render
@ -457,45 +544,16 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
.filter(|font| font.has_glyphs())
.or_else(|| context.library.device_font())
{
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 = edit_text.text.chars().peekable();
let has_kerning_info = font.has_kerning_info();
while let Some(c) = chars.next() {
// TODO: SWF text fields can contain a limited subset of HTML (and often do in SWF versions >6).
// This is a quicky-and-dirty way to skip the HTML tags. This is obviously not correct
// and we will need to properly parse and handle the HTML at some point.
// See SWF19 pp. 173-174 for supported HTML tags.
if edit_text.static_data.0.is_html && c == '<' {
// Skip characters until we see a close bracket.
chars.by_ref().skip_while(|&x| x != '>').next();
} else if let Some(glyph) = font.get_glyph_for_char(c) {
// Render glyph.
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;
}
}
self.evaluate_font(font, &edit_text.text, |transform, glyph: &Glyph| {
// Render glyph.
context.transform_stack.push(transform);
context
.renderer
.render_shape(glyph.shape, context.transform_stack.transform());
context.transform_stack.pop();
});
}
context.transform_stack.pop();
}