Collect font height, letter spacing and the kerning flag into a single `EvalParameters` structure.

This commit is contained in:
David Wendt 2020-06-10 19:31:51 -04:00
parent 1f6d6018dc
commit d8a38d06bb
4 changed files with 89 additions and 116 deletions

View File

@ -443,16 +443,12 @@ impl<'gc> EditText<'gc> {
// 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((start, end, _tf, font, font_size, letter_spacing, kerning, color)) =
lbox.read().text_node()
{
if let Some((start, end, _tf, font, params, color)) = lbox.read().text_node() {
if let Some(chunk) = edit_text.text_spans.text().get(start..end) {
font.evaluate(
&chunk,
self.text_transform(color),
font_size,
letter_spacing,
kerning,
params,
|transform, glyph: &Glyph, _advance| {
// Render glyph.
context.transform_stack.push(transform);

View File

@ -1,4 +1,5 @@
use crate::backend::render::{RenderBackend, ShapeHandle};
use crate::html::TextSpan;
use crate::prelude::*;
use crate::transform::Transform;
use gc_arena::{Collect, Gc, MutationContext};
@ -10,6 +11,47 @@ pub fn round_down_to_pixel(t: Twips) -> Twips {
type Error = Box<dyn std::error::Error>;
/// Parameters necessary to evaluate a font.
#[derive(Copy, Clone, Debug, Collect)]
#[collect(require_static)]
pub struct EvalParameters {
/// The height of each glyph, equivalent to a font size.
height: Twips,
/// Additional letter spacing to be added to or removed from each glyph
/// after normal or kerned glyph advances are applied.
letter_spacing: Twips,
/// Whether or not to allow use of font-provided kerning metrics.
///
/// Fonts can optionally add or remove additional spacing between specific
/// pairs of letters, separate from the ordinary width between glyphs. This
/// parameter allows enabling or disabling that feature.
kerning: bool,
}
impl EvalParameters {
/// Construct eval parameters from their individual parts.
#[allow(dead_code)]
fn from_parts(height: Twips, letter_spacing: Twips, kerning: bool) -> Self {
Self {
height,
letter_spacing,
kerning,
}
}
/// Convert the formatting on a text span over to font evaluation
/// parameters.
pub fn from_span(span: &TextSpan) -> Self {
Self {
height: Twips::from_pixels(span.size),
letter_spacing: Twips::from_pixels(span.letter_spacing),
kerning: span.kerning,
}
}
}
#[derive(Debug, Clone, Collect, Copy)]
#[collect(no_drop)]
pub struct Font<'gc>(Gc<'gc, FontData>);
@ -167,15 +209,13 @@ impl<'gc> Font<'gc> {
self,
text: &str,
mut transform: Transform,
height: Twips,
letter_spacing: Twips,
kerning: bool,
params: EvalParameters,
mut glyph_func: FGlyph,
) where
FGlyph: FnMut(&Transform, &Glyph, Twips),
{
transform.matrix.ty += height;
let scale = height.get() as f32 / self.scale();
transform.matrix.ty += params.height;
let scale = params.height.get() as f32 / self.scale();
transform.matrix.a = scale;
transform.matrix.d = scale;
@ -184,11 +224,11 @@ impl<'gc> Font<'gc> {
while let Some(c) = chars.next() {
if let Some(glyph) = self.get_glyph_for_char(c) {
let mut advance = Twips::new(glyph.advance);
if has_kerning_info && kerning {
if has_kerning_info && params.kerning {
advance += self.get_kerning_offset(c, chars.peek().cloned().unwrap_or('\0'));
}
let twips_advance =
Twips::new((advance.get() as f32 * scale) as i32) + letter_spacing;
Twips::new((advance.get() as f32 * scale) as i32) + params.letter_spacing;
glyph_func(&transform, &glyph, twips_advance);
@ -202,22 +242,13 @@ impl<'gc> Font<'gc> {
///
/// The `round` flag causes the returned coordinates to be rounded down to
/// the nearest pixel.
pub fn measure(
self,
text: &str,
font_size: Twips,
letter_spacing: Twips,
kerning: bool,
round: bool,
) -> (Twips, Twips) {
pub fn measure(self, text: &str, params: EvalParameters, round: bool) -> (Twips, Twips) {
let mut size = (Twips::new(0), Twips::new(0));
self.evaluate(
text,
Default::default(),
font_size,
letter_spacing,
kerning,
params,
|transform, _glyph, advance| {
let tx = transform.matrix.tx;
let ty = transform.matrix.ty;
@ -254,9 +285,7 @@ impl<'gc> Font<'gc> {
pub fn wrap_line(
self,
text: &str,
font_size: Twips,
letter_spacing: Twips,
kerning: bool,
params: EvalParameters,
width: Twips,
offset: Twips,
mut is_start_of_line: bool,
@ -276,9 +305,7 @@ impl<'gc> Font<'gc> {
let measure = self.measure(
text.get(word_start..word_end + 1).unwrap_or(word),
font_size,
letter_spacing,
kerning,
params,
true,
);
@ -288,13 +315,8 @@ impl<'gc> Font<'gc> {
let mut frag_end = word_start;
while last_passing_breakpoint.0 < remaining_width {
frag_end += 1;
last_passing_breakpoint = self.measure(
text.get(word_start..frag_end).unwrap(),
font_size,
letter_spacing,
kerning,
true,
);
last_passing_breakpoint =
self.measure(text.get(word_start..frag_end).unwrap(), params, true);
}
return Some(frag_end - 1);
@ -377,7 +399,7 @@ impl FontDescriptor {
#[cfg(test)]
mod tests {
use crate::backend::render::{NullRenderer, RenderBackend};
use crate::font::Font;
use crate::font::{EvalParameters, Font};
use crate::player::{Player, DEVICE_FONT_TAG};
use gc_arena::{rootless_arena, MutationContext};
use swf::Twips;
@ -397,12 +419,12 @@ mod tests {
#[test]
fn wrap_line_no_breakpoint() {
with_device_font(|_mc, df| {
let params =
EvalParameters::from_parts(Twips::from_pixels(12.0), Twips::from_pixels(0.0), true);
let string = "abcdefghijklmnopqrstuv";
let breakpoint = df.wrap_line(
&string,
Twips::from_pixels(12.0),
Twips::from_pixels(0.0),
true,
params,
Twips::from_pixels(200.0),
Twips::from_pixels(0.0),
true,
@ -415,13 +437,13 @@ mod tests {
#[test]
fn wrap_line_breakpoint_every_word() {
with_device_font(|_mc, df| {
let params =
EvalParameters::from_parts(Twips::from_pixels(12.0), Twips::from_pixels(0.0), true);
let string = "abcd efgh ijkl mnop";
let mut last_bp = 0;
let breakpoint = df.wrap_line(
&string,
Twips::from_pixels(12.0),
Twips::from_pixels(0.0),
true,
params,
Twips::from_pixels(35.0),
Twips::from_pixels(0.0),
true,
@ -433,9 +455,7 @@ mod tests {
let breakpoint2 = df.wrap_line(
&string[last_bp..],
Twips::from_pixels(12.0),
Twips::from_pixels(0.0),
true,
params,
Twips::from_pixels(35.0),
Twips::from_pixels(0.0),
true,
@ -447,9 +467,7 @@ mod tests {
let breakpoint3 = df.wrap_line(
&string[last_bp..],
Twips::from_pixels(12.0),
Twips::from_pixels(0.0),
true,
params,
Twips::from_pixels(35.0),
Twips::from_pixels(0.0),
true,
@ -461,9 +479,7 @@ mod tests {
let breakpoint4 = df.wrap_line(
&string[last_bp..],
Twips::from_pixels(12.0),
Twips::from_pixels(0.0),
true,
params,
Twips::from_pixels(35.0),
Twips::from_pixels(0.0),
true,
@ -476,12 +492,12 @@ mod tests {
#[test]
fn wrap_line_breakpoint_no_room() {
with_device_font(|_mc, df| {
let params =
EvalParameters::from_parts(Twips::from_pixels(12.0), Twips::from_pixels(0.0), true);
let string = "abcd efgh ijkl mnop";
let breakpoint = df.wrap_line(
&string,
Twips::from_pixels(12.0),
Twips::from_pixels(0.0),
true,
params,
Twips::from_pixels(30.0),
Twips::from_pixels(29.0),
false,
@ -494,13 +510,13 @@ mod tests {
#[test]
fn wrap_line_breakpoint_irregular_sized_words() {
with_device_font(|_mc, df| {
let params =
EvalParameters::from_parts(Twips::from_pixels(12.0), Twips::from_pixels(0.0), true);
let string = "abcdi j kl mnop q rstuv";
let mut last_bp = 0;
let breakpoint = df.wrap_line(
&string,
Twips::from_pixels(12.0),
Twips::from_pixels(0.0),
true,
params,
Twips::from_pixels(37.0),
Twips::from_pixels(0.0),
true,
@ -512,9 +528,7 @@ mod tests {
let breakpoint2 = df.wrap_line(
&string[last_bp..],
Twips::from_pixels(12.0),
Twips::from_pixels(0.0),
true,
params,
Twips::from_pixels(37.0),
Twips::from_pixels(0.0),
true,
@ -526,9 +540,7 @@ mod tests {
let breakpoint3 = df.wrap_line(
&string[last_bp..],
Twips::from_pixels(12.0),
Twips::from_pixels(0.0),
true,
params,
Twips::from_pixels(37.0),
Twips::from_pixels(0.0),
true,
@ -540,9 +552,7 @@ mod tests {
let breakpoint4 = df.wrap_line(
&string[last_bp..],
Twips::from_pixels(12.0),
Twips::from_pixels(0.0),
true,
params,
Twips::from_pixels(37.0),
Twips::from_pixels(0.0),
true,
@ -554,9 +564,7 @@ mod tests {
let breakpoint5 = df.wrap_line(
&string[last_bp..],
Twips::from_pixels(12.0),
Twips::from_pixels(0.0),
true,
params,
Twips::from_pixels(37.0),
Twips::from_pixels(0.0),
true,

View File

@ -9,7 +9,7 @@ pub use dimensions::BoxBounds;
pub use dimensions::Position;
pub use dimensions::Size;
pub use layout::LayoutBox;
pub use text_format::{FormatSpans, TextFormat};
pub use text_format::{FormatSpans, TextFormat, TextSpan};
#[cfg(test)]
mod test;

View File

@ -1,7 +1,7 @@
//! Layout box structure
use crate::context::UpdateContext;
use crate::font::Font;
use crate::font::{EvalParameters, Font};
use crate::html::dimensions::{BoxBounds, Position, Size};
use crate::html::text_format::{FormatSpans, TextFormat, TextSpan};
use crate::tag_utils::SwfMovie;
@ -124,16 +124,13 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
while let Some(linebox) = line {
let mut write = linebox.write(mc);
line = write.next_sibling();
let (start, end, _tf, font, size, letter_spacing, kerning, _color) =
write.text_node().expect("text");
let (start, end, _tf, font, params, _color) = write.text_node().expect("text");
//Flash ignores trailing spaces when aligning lines, so should we
if self.current_line_span.align != swf::TextAlign::Left {
write.bounds = write.bounds.with_size(Size::from(font.measure(
self.text[start..end].trim_end(),
size,
letter_spacing,
kerning,
params,
false,
)));
}
@ -342,15 +339,8 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
end: usize,
span: &TextSpan,
) {
let font_size = Twips::from_pixels(span.size);
let letter_spacing = Twips::from_pixels(span.letter_spacing);
let text_size = Size::from(self.font.unwrap().measure(
text,
font_size,
letter_spacing,
span.kerning,
false,
));
let params = EvalParameters::from_span(span);
let text_size = Size::from(self.font.unwrap().measure(text, params, false));
let text_bounds = BoxBounds::from_position_and_size(self.cursor, text_size);
let new_text = LayoutBox::from_text(mc, start, end, self.font.unwrap(), span);
let mut write = new_text.write(mc);
@ -480,9 +470,7 @@ pub enum LayoutContent<'gc> {
end: usize,
text_format: TextFormat,
font: Font<'gc>,
font_size: Collec<Twips>,
letter_spacing: Collec<Twips>,
kerning: bool,
params: EvalParameters,
color: Collec<swf::Color>,
},
}
@ -496,8 +484,7 @@ impl<'gc> LayoutBox<'gc> {
font: Font<'gc>,
span: &TextSpan,
) -> GcCell<'gc, Self> {
let font_size = Twips::from_pixels(span.size);
let letter_spacing = Twips::from_pixels(span.letter_spacing);
let params = EvalParameters::from_span(span);
GcCell::allocate(
mc,
@ -509,9 +496,7 @@ impl<'gc> LayoutBox<'gc> {
end,
text_format: span.get_text_format(),
font,
font_size: Collec(font_size),
letter_spacing: Collec(letter_spacing),
kerning: span.kerning,
params,
color: Collec(span.color.clone()),
},
},
@ -540,8 +525,7 @@ impl<'gc> LayoutBox<'gc> {
if let Some(font) = layout_context.resolve_font(context, movie.clone(), &span) {
layout_context.newspan(span);
let font_size = Twips::from_pixels(span.size);
let letter_spacing = Twips::from_pixels(span.letter_spacing);
let params = EvalParameters::from_span(span);
for text in span_text.split(&['\n', '\t'][..]) {
let slice_start = text.as_ptr() as usize - span_text.as_ptr() as usize;
@ -568,9 +552,7 @@ impl<'gc> LayoutBox<'gc> {
while let Some(breakpoint) = font.wrap_line(
&text[last_breakpoint..],
font_size,
letter_spacing,
span.kerning,
params,
width,
offset,
layout_context.is_start_of_line(),
@ -645,9 +627,7 @@ impl<'gc> LayoutBox<'gc> {
usize,
&TextFormat,
Font<'gc>,
Twips,
Twips,
bool,
EvalParameters,
swf::Color,
)> {
match &self.content {
@ -656,20 +636,9 @@ impl<'gc> LayoutBox<'gc> {
end,
text_format,
font,
font_size,
letter_spacing,
kerning,
params,
color,
} => Some((
*start,
*end,
&text_format,
*font,
font_size.0,
letter_spacing.0,
*kerning,
color.0.clone(),
)),
} => Some((*start, *end, &text_format, *font, *params, color.0.clone())),
}
}