Implement the `leading` attribute as defined by fonts.

`EditText` supports two different forms of leading:

1. Font-provided leading, specified relative to the EM square and scaled with font size
2. User-specificed leading, specified in pixels

Notably, the former appears to apply to the first line in the text and pushes it down. This showed up in the `edittext_font_size` test, and according to that test result the leading is rounded *up* to the nearest pixel, plus one.

That last bit seems possibly wrong and is subject to further change, but it matches the tests at multiple scales.
This commit is contained in:
David Wendt 2020-05-28 23:38:19 -04:00
parent 6e81f30a70
commit 06dc2f5fe0
6 changed files with 95 additions and 7 deletions

View File

@ -3,11 +3,16 @@ use crate::prelude::*;
use crate::transform::Transform;
use gc_arena::{Collect, Gc, MutationContext};
/// Certain Flash routines measure text up to the nearest whole pixel.
fn round_to_pixel(t: Twips) -> Twips {
/// Certain Flash routines measure text by rounding down to the nearest whole pixel.
fn round_down_to_pixel(t: Twips) -> Twips {
Twips::from_pixels(t.to_pixels().floor())
}
/// Certain Flash routines measure text by rounding up to the nearest whole pixel.
pub fn round_up_to_pixel(t: Twips) -> Twips {
Twips::from_pixels(t.to_pixels().ceil())
}
type Error = Box<dyn std::error::Error>;
#[derive(Debug, Clone, Collect, Copy)]
@ -33,6 +38,18 @@ struct FontData {
/// Maps from a pair of unicode code points to horizontal offset value.
kerning_pairs: fnv::FnvHashMap<(u16, u16), Twips>,
/// The distance from the top of each glyph to the baseline of the font, in
/// EM-square coordinates.
ascent: u16,
/// The distance from the baseline of the font to the bottom of each glyph,
/// in EM-square coordinates.
descent: u16,
/// The distance between the bottom of any one glyph and the top of
/// another, in EM-square coordinates.
leading: i16,
/// The identity of the font.
descriptor: FontDescriptor,
}
@ -65,6 +82,11 @@ impl<'gc> Font<'gc> {
};
let descriptor = FontDescriptor::from_swf_tag(tag);
let (ascent, descent, leading) = if let Some(layout) = &tag.layout {
(layout.ascent, layout.descent, layout.leading)
} else {
(0, 0, 0)
};
Ok(Font(Gc::allocate(
gc_context,
@ -76,6 +98,9 @@ impl<'gc> Font<'gc> {
/// (SWF19 p.164)
scale: if tag.version >= 3 { 20480.0 } else { 1024.0 },
kerning_pairs,
ascent,
descent,
leading,
descriptor,
},
)))
@ -119,6 +144,13 @@ impl<'gc> Font<'gc> {
.unwrap_or_default()
}
/// Return the leading for this font at a given height.
pub fn get_leading_for_height(self, height: Twips) -> Twips {
let scale = height.get() as f32 / self.scale();
Twips::new((self.0.leading as f32 * scale) as i32)
}
/// Returns whether this font contains kerning information.
pub fn has_kerning_info(self) -> bool {
!self.0.kerning_pairs.is_empty()
@ -179,8 +211,8 @@ impl<'gc> Font<'gc> {
|transform, _glyph, advance| {
let tx = transform.matrix.tx;
let ty = transform.matrix.ty;
size.0 = std::cmp::max(size.0, round_to_pixel(tx + advance));
size.1 = std::cmp::max(size.1, round_to_pixel(ty));
size.0 = std::cmp::max(size.0, round_down_to_pixel(tx + advance));
size.1 = std::cmp::max(size.1, round_down_to_pixel(ty));
},
);

View File

@ -1,7 +1,7 @@
//! Layout box structure
use crate::context::UpdateContext;
use crate::font::Font;
use crate::font::{round_up_to_pixel, Font};
use crate::html::dimensions::{BoxBounds, Position, Size};
use crate::html::text_format::{FormatSpans, TextFormat, TextSpan};
use crate::tag_utils::SwfMovie;
@ -117,6 +117,14 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
Twips::from_pixels(0.0),
);
// Flash appears to round up the font's leading to the nearest pixel
// and adds one. I'm not sure why.
let font_leading_adjustment = round_up_to_pixel(
self.font
.map(|f| f.get_leading_for_height(self.max_font_size))
.unwrap_or_else(|| Twips::new(0)),
) + Twips::from_pixels(1.0);
line = self.current_line;
while let Some(linebox) = line {
let mut write = linebox.write(mc);
@ -126,8 +134,10 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
// which is information we don't have yet.
let font_size_adjustment = self.max_font_size - write.bounds.height();
write.bounds +=
Position::from((left_adjustment + align_adjustment, font_size_adjustment));
write.bounds += Position::from((
left_adjustment + align_adjustment,
font_size_adjustment + font_leading_adjustment,
));
line = write.next_sibling();
}

View File

@ -197,6 +197,7 @@ swf_tests! {
(as1_constructor_v6, "avm1/as1_constructor_v6", 1),
(as1_constructor_v7, "avm1/as1_constructor_v7", 1),
(issue_710, "avm1/issue_710", 1),
(edittext_font_size, "avm1/edittext_font_size", 1),
}
// TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough.

View File

@ -0,0 +1,45 @@
//Creating left aligned text @ 10px
W: 7
H: 15
//Creating left aligned text @ 12px
W: 9
H: 18
//Creating left aligned text @ 20px
W: 15
H: 29
//Creating left aligned text @ 30px
W: 23
H: 42
//Creating left aligned text @ 100px
W: 78
H: 138
//Creating left aligned text 'Qe' @ 10px
W: 13
H: 15
//Creating left aligned text 'Qe' @ 12px
W: 16
H: 18
//Creating left aligned text 'Qe' @ 20px
W: 26
H: 29
//Creating left aligned text 'Qe' @ 30px
W: 40
H: 42
//Creating left aligned text 'Qe' @ 100px
W: 134
H: 138
//Creating left aligned text 'Q e' @ 10px
W: 15
H: 15
//Creating left aligned text 'Q e' @ 12px
W: 19
H: 18
//Creating left aligned text 'Q e' @ 20px
W: 32
H: 29
//Creating left aligned text 'Q e' @ 30px
W: 48
H: 42
//Creating left aligned text 'Q e' @ 100px
W: 160
H: 138

Binary file not shown.

Binary file not shown.