core: Make Library::device_font optional

This commit is contained in:
Mike Welsh 2019-10-25 23:44:12 -07:00
parent 57a737357b
commit 09fa755405
3 changed files with 82 additions and 60 deletions

View File

@ -65,52 +65,53 @@ impl<'gc> DisplayObject<'gc> for EditText<'gc> {
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 device_font = context.library.device_font();
// 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.
let font = context
if let Some(font) = context
.library
.get_font(font_id)
.filter(|font| font.has_glyphs())
.unwrap_or(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 = self.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 self.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.
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;
.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 = self.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 self.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.
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;
}
transform.matrix.tx += advance * scale;
}
}
context.transform_stack.pop();

View File

@ -10,15 +10,15 @@ use swf::CharacterId;
pub struct Library<'gc> {
characters: HashMap<CharacterId, Character<'gc>>,
jpeg_tables: Option<Vec<u8>>,
device_font: Box<Font>,
device_font: Option<Box<Font>>,
}
impl<'gc> Library<'gc> {
pub fn new(device_font: Box<Font>) -> Self {
pub fn new() -> Self {
Library {
characters: HashMap::new(),
jpeg_tables: None,
device_font,
device_font: None,
}
}
@ -95,8 +95,13 @@ impl<'gc> Library<'gc> {
}
/// Returns the device font for use when a font is unavailable.
pub fn device_font(&self) -> &Font {
&*self.device_font
pub fn device_font(&self) -> Option<&Font> {
self.device_font.as_ref().map(AsRef::as_ref)
}
/// Sets the device font.
pub fn set_device_font(&mut self, font: Option<Box<Font>>) {
self.device_font = font;
}
}
@ -108,3 +113,9 @@ unsafe impl<'gc> gc_arena::Collect for Library<'gc> {
}
}
}
impl Default for Library<'_> {
fn default() -> Self {
Self::new()
}
}

View File

@ -119,8 +119,14 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
// Load and parse the device font.
// TODO: We could use lazy_static here.
let device_font = Self::load_device_font(DEVICE_FONT_TAG, &mut renderer)
.expect("Unable to load device font");
let device_font = match Self::load_device_font(DEVICE_FONT_TAG, &mut renderer) {
Ok(font) => Some(font),
Err(e) => {
log::error!("Unable to load device font: {}", e);
None
}
};
let mut player = Player {
player_version: NEWEST_PLAYER_VERSION,
@ -145,22 +151,26 @@ impl<Audio: AudioBackend, Renderer: RenderBackend, Navigator: NavigatorBackend>
rng: SmallRng::from_seed([0u8; 16]), // TODO(Herschel): Get a proper seed on all platforms.
gc_arena: GcArena::new(ArenaParameters::default(), |gc_context| GcRoot {
library: GcCell::allocate(gc_context, Library::new(device_font)),
root: GcCell::allocate(
gc_context,
Box::new(MovieClip::new_with_data(
header.version,
gc_arena: GcArena::new(ArenaParameters::default(), |gc_context| {
let mut library = Library::new();
library.set_device_font(device_font);
GcRoot {
library: GcCell::allocate(gc_context, library),
root: GcCell::allocate(
gc_context,
0,
0,
swf_len,
header.num_frames,
)),
),
mouse_hover_node: GcCell::allocate(gc_context, None),
avm: GcCell::allocate(gc_context, Avm1::new(gc_context, NEWEST_PLAYER_VERSION)),
action_queue: GcCell::allocate(gc_context, ActionQueue::new()),
Box::new(MovieClip::new_with_data(
header.version,
gc_context,
0,
0,
swf_len,
header.num_frames,
)),
),
mouse_hover_node: GcCell::allocate(gc_context, None),
avm: GcCell::allocate(gc_context, Avm1::new(gc_context, NEWEST_PLAYER_VERSION)),
action_queue: GcCell::allocate(gc_context, ActionQueue::new()),
}
}),
frame_rate: header.frame_rate.into(),