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>(
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
self.evaluate_font(font, &edit_text.text, |transform, glyph: &Glyph| {
|
||||
// Render glyph.
|
||||
context.transform_stack.push(&transform);
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue