Impl `textWidth` / `textHeight`, although it currently only works well for single-line scenarios.
This commit is contained in:
parent
2181f0d0d0
commit
81b7958090
|
@ -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>(
|
pub fn create_proto<'gc>(
|
||||||
gc_context: MutationContext<'gc, '_>,
|
gc_context: MutationContext<'gc, '_>,
|
||||||
proto: Object<'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()
|
object.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ use crate::avm1::globals::text_field::attach_virtual_properties;
|
||||||
use crate::avm1::{Avm1, Object, ScriptObject, StageObject, TObject, Value};
|
use crate::avm1::{Avm1, Object, ScriptObject, StageObject, TObject, Value};
|
||||||
use crate::context::{RenderContext, UpdateContext};
|
use crate::context::{RenderContext, UpdateContext};
|
||||||
use crate::display_object::{DisplayObjectBase, TDisplayObject};
|
use crate::display_object::{DisplayObjectBase, TDisplayObject};
|
||||||
|
use crate::font::{Font, Glyph};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::transform::Transform;
|
use crate::transform::Transform;
|
||||||
use gc_arena::{Collect, Gc, GcCell, MutationContext};
|
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, '_>) {
|
pub fn set_new_text_format(self, tf: TextFormat, gc_context: MutationContext<'gc, '_>) {
|
||||||
self.0.write(gc_context).new_format = tf;
|
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> {
|
impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
|
||||||
|
@ -428,25 +526,14 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
|
||||||
.unwrap_or(Value::Undefined)
|
.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();
|
let edit_text = self.0.read();
|
||||||
// TODO: This is a stub implementation to just get some dynamic text rendering.
|
// 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 static_data = &edit_text.static_data.0;
|
||||||
let font_id = static_data.font_id.unwrap_or(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.
|
// 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.
|
// 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
|
// 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())
|
.filter(|font| font.has_glyphs())
|
||||||
.or_else(|| context.library.device_font())
|
.or_else(|| context.library.device_font())
|
||||||
{
|
{
|
||||||
let scale = if let Some(height) = static_data.height {
|
self.evaluate_font(font, &edit_text.text, |transform, glyph: &Glyph| {
|
||||||
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.
|
// Render glyph.
|
||||||
context.transform_stack.push(&transform);
|
context.transform_stack.push(transform);
|
||||||
context
|
context
|
||||||
.renderer
|
.renderer
|
||||||
.render_shape(glyph.shape, context.transform_stack.transform());
|
.render_shape(glyph.shape, context.transform_stack.transform());
|
||||||
context.transform_stack.pop();
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue