2019-07-19 08:32:41 +00:00
|
|
|
use crate::backend::render::{RenderBackend, ShapeHandle};
|
2019-10-08 02:27:31 +00:00
|
|
|
use crate::prelude::*;
|
2020-01-19 23:17:19 +00:00
|
|
|
use crate::transform::Transform;
|
2019-12-17 05:21:59 +00:00
|
|
|
use gc_arena::{Collect, Gc, MutationContext};
|
2019-05-04 18:45:11 +00:00
|
|
|
|
2019-08-15 20:48:51 +00:00
|
|
|
type Error = Box<dyn std::error::Error>;
|
2019-05-04 18:45:11 +00:00
|
|
|
|
2020-01-20 00:04:00 +00:00
|
|
|
mod text_format;
|
|
|
|
|
|
|
|
pub use text_format::TextFormat;
|
|
|
|
|
2019-12-17 05:21:59 +00:00
|
|
|
#[derive(Debug, Clone, Collect, Copy)]
|
|
|
|
#[collect(no_drop)]
|
|
|
|
pub struct Font<'gc>(Gc<'gc, FontData>);
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Collect)]
|
|
|
|
#[collect(require_static)]
|
|
|
|
struct FontData {
|
2019-10-08 02:27:31 +00:00
|
|
|
/// The list of glyphs defined in the font.
|
|
|
|
/// Used directly by `DefineText` tags.
|
|
|
|
glyphs: Vec<Glyph>,
|
|
|
|
|
|
|
|
/// A map from a Unicode code point to glyph in the `glyphs` array.
|
|
|
|
/// Used by `DefineEditText` tags.
|
|
|
|
code_point_to_glyph: fnv::FnvHashMap<u16, usize>,
|
2019-10-07 18:39:44 +00:00
|
|
|
|
|
|
|
/// The scaling applied to the font height to render at the proper size.
|
|
|
|
/// This depends on the DefineFont tag version.
|
|
|
|
scale: f32,
|
2019-10-08 02:27:31 +00:00
|
|
|
|
|
|
|
/// Kerning infomration.
|
|
|
|
/// Maps from a pair of unicode code points to horizontal offset value.
|
|
|
|
kerning_pairs: fnv::FnvHashMap<(u16, u16), Twips>,
|
2019-05-04 18:45:11 +00:00
|
|
|
}
|
|
|
|
|
2019-12-17 05:21:59 +00:00
|
|
|
impl<'gc> Font<'gc> {
|
|
|
|
pub fn from_swf_tag(
|
|
|
|
gc_context: MutationContext<'gc, '_>,
|
|
|
|
renderer: &mut dyn RenderBackend,
|
|
|
|
tag: &swf::Font,
|
|
|
|
) -> Result<Font<'gc>, Error> {
|
2019-05-04 18:45:11 +00:00
|
|
|
let mut glyphs = vec![];
|
2019-10-08 02:27:31 +00:00
|
|
|
let mut code_point_to_glyph = fnv::FnvHashMap::default();
|
|
|
|
for swf_glyph in &tag.glyphs {
|
|
|
|
let glyph = Glyph {
|
|
|
|
shape: renderer.register_glyph_shape(swf_glyph),
|
|
|
|
advance: swf_glyph.advance.unwrap_or(0),
|
|
|
|
};
|
|
|
|
let index = glyphs.len();
|
|
|
|
glyphs.push(glyph);
|
|
|
|
code_point_to_glyph.insert(swf_glyph.code, index);
|
2019-05-04 18:45:11 +00:00
|
|
|
}
|
2019-10-08 02:27:31 +00:00
|
|
|
let kerning_pairs: fnv::FnvHashMap<(u16, u16), Twips> = if let Some(layout) = &tag.layout {
|
|
|
|
layout
|
|
|
|
.kerning
|
|
|
|
.iter()
|
|
|
|
.map(|kerning| ((kerning.left_code, kerning.right_code), kerning.adjustment))
|
|
|
|
.collect()
|
|
|
|
} else {
|
|
|
|
fnv::FnvHashMap::default()
|
|
|
|
};
|
2019-12-17 05:21:59 +00:00
|
|
|
Ok(Font(Gc::allocate(
|
|
|
|
gc_context,
|
|
|
|
FontData {
|
|
|
|
glyphs,
|
|
|
|
code_point_to_glyph,
|
2019-10-07 18:39:44 +00:00
|
|
|
|
2019-12-17 05:21:59 +00:00
|
|
|
/// DefineFont3 stores coordinates at 20x the scale of DefineFont1/2.
|
|
|
|
/// (SWF19 p.164)
|
|
|
|
scale: if tag.version >= 3 { 20480.0 } else { 1024.0 },
|
|
|
|
kerning_pairs,
|
|
|
|
},
|
|
|
|
)))
|
2019-05-04 18:45:11 +00:00
|
|
|
}
|
|
|
|
|
2019-10-08 04:02:09 +00:00
|
|
|
/// Returns whether this font contains glyph shapes.
|
|
|
|
/// If not, this font should be rendered as a device font.
|
2019-12-17 05:21:59 +00:00
|
|
|
pub fn has_glyphs(self) -> bool {
|
|
|
|
!self.0.glyphs.is_empty()
|
2019-10-08 04:02:09 +00:00
|
|
|
}
|
|
|
|
|
2019-10-08 02:27:31 +00:00
|
|
|
/// Returns a glyph entry by index.
|
|
|
|
/// Used by `Text` display objects.
|
2019-12-17 05:21:59 +00:00
|
|
|
pub fn get_glyph(self, i: usize) -> Option<Glyph> {
|
|
|
|
self.0.glyphs.get(i).cloned()
|
2019-05-04 18:45:11 +00:00
|
|
|
}
|
2019-10-07 18:39:44 +00:00
|
|
|
|
2019-10-08 02:27:31 +00:00
|
|
|
/// Returns a glyph entry by character.
|
|
|
|
/// Used by `EditText` display objects.
|
2019-12-17 05:21:59 +00:00
|
|
|
pub fn get_glyph_for_char(self, c: char) -> Option<Glyph> {
|
2019-10-08 02:27:31 +00:00
|
|
|
// TODO: Properly handle UTF-16/out-of-bounds code points.
|
|
|
|
let code_point = c as u16;
|
2019-12-17 05:21:59 +00:00
|
|
|
if let Some(index) = self.0.code_point_to_glyph.get(&code_point) {
|
2019-10-08 02:27:31 +00:00
|
|
|
self.get_glyph(*index)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Given a pair of characters, applies the offset that should be applied
|
|
|
|
/// to the advance value between these two characters.
|
|
|
|
/// Returns 0 twips if no kerning offset exists between these two characters.
|
2019-12-17 05:21:59 +00:00
|
|
|
pub fn get_kerning_offset(self, left: char, right: char) -> Twips {
|
2019-10-08 02:27:31 +00:00
|
|
|
// TODO: Properly handle UTF-16/out-of-bounds code points.
|
|
|
|
let left_code_point = left as u16;
|
|
|
|
let right_code_point = right as u16;
|
2019-12-17 05:21:59 +00:00
|
|
|
self.0
|
|
|
|
.kerning_pairs
|
2019-10-08 02:27:31 +00:00
|
|
|
.get(&(left_code_point, right_code_point))
|
|
|
|
.cloned()
|
|
|
|
.unwrap_or_default()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns whether this font contains kerning information.
|
2019-12-17 05:21:59 +00:00
|
|
|
pub fn has_kerning_info(self) -> bool {
|
|
|
|
!self.0.kerning_pairs.is_empty()
|
2019-10-08 02:27:31 +00:00
|
|
|
}
|
|
|
|
|
2019-12-17 05:21:59 +00:00
|
|
|
pub fn scale(self) -> f32 {
|
|
|
|
self.0.scale
|
2019-10-07 18:39:44 +00:00
|
|
|
}
|
2020-01-19 23:17:19 +00:00
|
|
|
|
|
|
|
/// Evaluate this font against a particular string on a glyph-by-glyph
|
|
|
|
/// basis.
|
|
|
|
///
|
|
|
|
/// This function takes the text string to evaluate against, the base
|
|
|
|
/// transform to start from, the height of each glyph, and produces a list
|
|
|
|
/// of transforms and glyphs which will be consumed by the `glyph_func`
|
|
|
|
/// closure. This corresponds to the series of drawing operations necessary
|
|
|
|
/// to render the text on a single horizontal line.
|
|
|
|
pub fn evaluate<FGlyph>(
|
|
|
|
self,
|
|
|
|
text: &str,
|
|
|
|
mut transform: Transform,
|
2020-02-18 19:39:53 +00:00
|
|
|
height: Twips,
|
2020-01-19 23:17:19 +00:00
|
|
|
mut glyph_func: FGlyph,
|
|
|
|
) where
|
|
|
|
FGlyph: FnMut(&Transform, &Glyph),
|
|
|
|
{
|
2020-02-18 19:39:53 +00:00
|
|
|
transform.matrix.ty += height;
|
|
|
|
let scale = height.get() as f32 / self.scale();
|
2020-01-19 23:17:19 +00:00
|
|
|
|
|
|
|
transform.matrix.a = scale;
|
|
|
|
transform.matrix.d = scale;
|
|
|
|
let mut chars = text.chars().peekable();
|
|
|
|
let has_kerning_info = self.has_kerning_info();
|
|
|
|
while let Some(c) = chars.next() {
|
2020-02-04 00:32:05 +00:00
|
|
|
if let Some(glyph) = self.get_glyph_for_char(c) {
|
2020-01-19 23:17:19 +00:00
|
|
|
glyph_func(&transform, &glyph);
|
|
|
|
// Step horizontally.
|
2020-02-18 19:39:53 +00:00
|
|
|
let mut advance = Twips::new(glyph.advance);
|
2020-01-19 23:17:19 +00:00
|
|
|
if has_kerning_info {
|
2020-02-18 19:39:53 +00:00
|
|
|
advance += self.get_kerning_offset(c, chars.peek().cloned().unwrap_or('\0'));
|
2020-01-19 23:17:19 +00:00
|
|
|
}
|
2020-02-18 19:39:53 +00:00
|
|
|
transform.matrix.tx += Twips::new((advance.get() as f32 * scale) as i32);
|
2020-01-19 23:17:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-20 03:02:21 +00:00
|
|
|
|
|
|
|
/// Measure a particular string's metrics (width and height).
|
2020-02-18 19:39:53 +00:00
|
|
|
pub fn measure(self, text: &str, height: Twips) -> (Twips, Twips) {
|
|
|
|
let mut size = (Twips::new(0), Twips::new(0));
|
2020-01-20 03:02:21 +00:00
|
|
|
|
2020-02-04 00:32:05 +00:00
|
|
|
self.evaluate(text, Default::default(), height, |transform, _glyph| {
|
2020-02-18 19:39:53 +00:00
|
|
|
let tx = transform.matrix.tx;
|
|
|
|
let ty = transform.matrix.ty;
|
|
|
|
size.0 = std::cmp::max(size.0, tx);
|
|
|
|
size.1 = std::cmp::max(size.1, ty);
|
2020-02-04 00:32:05 +00:00
|
|
|
});
|
2020-01-20 03:02:21 +00:00
|
|
|
|
|
|
|
size
|
|
|
|
}
|
2020-01-21 00:23:00 +00:00
|
|
|
|
|
|
|
/// Given a line of text, split it into the shortest number of lines that
|
|
|
|
/// are shorter than `width`.
|
|
|
|
///
|
|
|
|
/// This function assumes only `" "` is valid whitespace to split words on,
|
|
|
|
/// and will not attempt to break words that are longer than `width`.
|
2020-02-18 19:39:53 +00:00
|
|
|
pub fn split_wrapped_lines(self, text: &str, height: Twips, width: Twips) -> Vec<usize> {
|
2020-01-21 00:23:00 +00:00
|
|
|
let mut result = vec![];
|
|
|
|
let mut current_width = width;
|
|
|
|
let mut current_word = &text[0..0];
|
|
|
|
|
|
|
|
for word in text.split(' ') {
|
2020-02-04 00:32:05 +00:00
|
|
|
let measure = self.measure(word, height);
|
2020-01-21 00:23:00 +00:00
|
|
|
let line_start = current_word.as_ptr() as usize - text.as_ptr() as usize;
|
2020-01-21 19:33:17 +00:00
|
|
|
let line_end = if (line_start + current_word.len() + 1) < text.len() {
|
|
|
|
line_start + current_word.len() + 1
|
2020-01-21 00:23:00 +00:00
|
|
|
} else {
|
2020-01-21 19:33:17 +00:00
|
|
|
line_start + current_word.len()
|
|
|
|
};
|
|
|
|
let word_start = word.as_ptr() as usize - text.as_ptr() as usize;
|
|
|
|
let word_end = if (word_start + word.len() + 1) < text.len() {
|
|
|
|
word_start + word.len() + 1
|
|
|
|
} else {
|
|
|
|
word_start + word.len()
|
2020-01-21 00:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
if measure.0 > current_width && measure.0 > width {
|
|
|
|
//Failsafe for if we get a word wider than the field.
|
|
|
|
if !current_word.is_empty() {
|
2020-01-21 19:33:17 +00:00
|
|
|
result.push(line_end);
|
2020-01-21 00:23:00 +00:00
|
|
|
}
|
2020-01-21 19:33:17 +00:00
|
|
|
result.push(word_end);
|
|
|
|
current_word = &text[word_end..word_end];
|
2020-01-21 00:23:00 +00:00
|
|
|
current_width = width;
|
|
|
|
} else if measure.0 > current_width {
|
|
|
|
if !current_word.is_empty() {
|
2020-01-21 19:33:17 +00:00
|
|
|
result.push(line_end);
|
2020-01-21 00:23:00 +00:00
|
|
|
}
|
|
|
|
|
2020-01-21 19:33:17 +00:00
|
|
|
current_word = &text[word_start..word_end];
|
2020-01-21 00:23:00 +00:00
|
|
|
current_width = width;
|
|
|
|
} else {
|
2020-01-21 19:33:17 +00:00
|
|
|
current_word = &text[line_start..word_end];
|
2020-01-21 00:23:00 +00:00
|
|
|
current_width -= measure.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
2019-05-04 18:45:11 +00:00
|
|
|
}
|
2019-10-08 02:27:31 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct Glyph {
|
|
|
|
pub shape: ShapeHandle,
|
|
|
|
pub advance: i16,
|
|
|
|
}
|