core: Use correct embedded font fallback order + tests
This commit is contained in:
parent
8b5b135a2d
commit
64385efe48
|
@ -30,7 +30,7 @@ pub fn get_font_name<'gc>(
|
||||||
{
|
{
|
||||||
return Ok(AvmString::new_utf8(
|
return Ok(AvmString::new_utf8(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
font.descriptor().class(),
|
font.descriptor().name(),
|
||||||
)
|
)
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use ruffle_render::backend::{RenderBackend, ShapeHandle};
|
||||||
use ruffle_render::transform::Transform;
|
use ruffle_render::transform::Transform;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
pub use swf::TextGridFit;
|
pub use swf::TextGridFit;
|
||||||
|
|
||||||
|
@ -556,21 +557,47 @@ impl Glyph {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Structure which identifies a particular font by name and properties.
|
/// Structure which identifies a particular font by name and properties.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Collect)]
|
#[derive(Debug, Clone, Ord, PartialOrd, Collect)]
|
||||||
#[collect(require_static)]
|
#[collect(require_static)]
|
||||||
pub struct FontDescriptor {
|
pub struct FontDescriptor {
|
||||||
|
/// The name of the font.
|
||||||
|
/// This is set by the author of the SWF and does not correlate to any opentype names.
|
||||||
name: String,
|
name: String,
|
||||||
|
|
||||||
|
// All name comparisons ignore case, so this is for easy comparisons.
|
||||||
|
lowercase_name: String,
|
||||||
|
|
||||||
is_bold: bool,
|
is_bold: bool,
|
||||||
is_italic: bool,
|
is_italic: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for FontDescriptor {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.lowercase_name == other.lowercase_name
|
||||||
|
&& self.is_italic == other.is_italic
|
||||||
|
&& self.is_bold == other.is_bold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for FontDescriptor {}
|
||||||
|
|
||||||
|
impl Hash for FontDescriptor {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.lowercase_name.hash(state);
|
||||||
|
self.is_bold.hash(state);
|
||||||
|
self.is_italic.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FontDescriptor {
|
impl FontDescriptor {
|
||||||
/// Obtain a font descriptor from a SWF font tag.
|
/// Obtain a font descriptor from a SWF font tag.
|
||||||
pub fn from_swf_tag(val: &swf::Font, encoding: &'static swf::Encoding) -> Self {
|
pub fn from_swf_tag(val: &swf::Font, encoding: &'static swf::Encoding) -> Self {
|
||||||
let name = val.name.to_string_lossy(encoding);
|
let name = val.name.to_string_lossy(encoding);
|
||||||
|
let lowercase_name = name.to_lowercase();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
|
lowercase_name,
|
||||||
is_bold: val.flags.contains(swf::FontFlag::IS_BOLD),
|
is_bold: val.flags.contains(swf::FontFlag::IS_BOLD),
|
||||||
is_italic: val.flags.contains(swf::FontFlag::IS_ITALIC),
|
is_italic: val.flags.contains(swf::FontFlag::IS_ITALIC),
|
||||||
}
|
}
|
||||||
|
@ -583,16 +610,18 @@ impl FontDescriptor {
|
||||||
if let Some(first_null) = name.find('\0') {
|
if let Some(first_null) = name.find('\0') {
|
||||||
name.truncate(first_null);
|
name.truncate(first_null);
|
||||||
};
|
};
|
||||||
|
let lowercase_name = name.to_lowercase();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
|
lowercase_name,
|
||||||
is_bold,
|
is_bold,
|
||||||
is_italic,
|
is_italic,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the name of the font class this descriptor references.
|
/// Get the name of the font this descriptor identifies.
|
||||||
pub fn class(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -470,11 +470,15 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> {
|
||||||
// In an ideal world, device fonts would search for a matching font on the system and render it in some way.
|
// In an ideal world, device fonts would search for a matching font on the system and render it in some way.
|
||||||
if !is_device_font {
|
if !is_device_font {
|
||||||
if let Some(font) = library
|
if let Some(font) = library
|
||||||
.get_font_by_name(&font_name, span.bold, span.italic)
|
.get_embedded_font_by_name(&font_name, span.bold, span.italic)
|
||||||
.filter(|f| f.has_glyphs())
|
.filter(|f| f.has_glyphs())
|
||||||
{
|
{
|
||||||
return Some(font);
|
return Some(font);
|
||||||
}
|
}
|
||||||
|
// TODO: If set to use embedded fonts and we couldn't find any matching font, show nothing
|
||||||
|
// However - at time of writing, we don't support DefineFont4. If we matched this behaviour,
|
||||||
|
// then a bunch of SWFs would just show no text suddenly.
|
||||||
|
// return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(font) = context.library.get_or_load_device_font(
|
if let Some(font) = context.library.get_or_load_device_font(
|
||||||
|
|
|
@ -146,7 +146,7 @@ impl TextFormat {
|
||||||
let font_class = et
|
let font_class = et
|
||||||
.font_class()
|
.font_class()
|
||||||
.map(|s| s.decode(encoding).into_owned())
|
.map(|s| s.decode(encoding).into_owned())
|
||||||
.or_else(|| font.map(|font| WString::from_utf8(font.descriptor().class())))
|
.or_else(|| font.map(|font| WString::from_utf8(font.descriptor().name())))
|
||||||
.unwrap_or_else(|| WString::from_utf8("Times New Roman"));
|
.unwrap_or_else(|| WString::from_utf8("Times New Roman"));
|
||||||
let align = et.layout().map(|l| l.align);
|
let align = et.layout().map(|l| l.align);
|
||||||
let left_margin = et.layout().map(|l| l.left_margin.to_pixels());
|
let left_margin = et.layout().map(|l| l.left_margin.to_pixels());
|
||||||
|
|
|
@ -259,24 +259,88 @@ impl<'gc> MovieLibrary<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find a font by it's name and parameters.
|
/// Find a font by it's name and parameters.
|
||||||
pub fn get_font_by_name(
|
pub fn get_embedded_font_by_name(
|
||||||
&self,
|
&self,
|
||||||
name: &str,
|
name: &str,
|
||||||
is_bold: bool,
|
is_bold: bool,
|
||||||
is_italic: bool,
|
is_italic: bool,
|
||||||
) -> Option<Font<'gc>> {
|
) -> Option<Font<'gc>> {
|
||||||
let descriptor = FontDescriptor::from_parts(name, is_bold, is_italic);
|
// The order here is specific, and tested in `tests/swfs/fonts/embed_matching/fallback_preferences`
|
||||||
if let Some(font) = self.fonts.get(&descriptor) {
|
|
||||||
|
// Exact match
|
||||||
|
if let Some(font) = self
|
||||||
|
.fonts
|
||||||
|
.get(&FontDescriptor::from_parts(name, is_bold, is_italic))
|
||||||
|
{
|
||||||
return Some(*font);
|
return Some(*font);
|
||||||
}
|
}
|
||||||
// If we don't have a direct match, fallback to something with the same name
|
|
||||||
// [NA]TODO: This isn't *entirely* correct. I think we're storing fonts wrong.
|
if is_italic ^ is_bold {
|
||||||
// We might need to merge fonts as they're defined, and there should only be one font per name.
|
// If one is set (but not both), then try upgrading to bold italic...
|
||||||
self.fonts
|
if let Some(font) = self
|
||||||
.iter()
|
.fonts
|
||||||
.find(|(d, _)| d.class() == name)
|
.get(&FontDescriptor::from_parts(name, true, true))
|
||||||
.map(|(_, f)| f)
|
{
|
||||||
.copied()
|
return Some(*font);
|
||||||
|
}
|
||||||
|
|
||||||
|
// and then downgrading to regular
|
||||||
|
if let Some(font) = self
|
||||||
|
.fonts
|
||||||
|
.get(&FontDescriptor::from_parts(name, false, false))
|
||||||
|
{
|
||||||
|
return Some(*font);
|
||||||
|
}
|
||||||
|
|
||||||
|
// and then finally whichever one we don't have set
|
||||||
|
if let Some(font) = self
|
||||||
|
.fonts
|
||||||
|
.get(&FontDescriptor::from_parts(name, !is_bold, !is_italic))
|
||||||
|
{
|
||||||
|
return Some(*font);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We don't have an exact match and we were either looking for regular or bold-italic
|
||||||
|
|
||||||
|
if is_italic && is_bold {
|
||||||
|
// Do we have regular? (unless we already looked for it)
|
||||||
|
if let Some(font) = self
|
||||||
|
.fonts
|
||||||
|
.get(&FontDescriptor::from_parts(name, false, false))
|
||||||
|
{
|
||||||
|
return Some(*font);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we have bold?
|
||||||
|
if let Some(font) = self
|
||||||
|
.fonts
|
||||||
|
.get(&FontDescriptor::from_parts(name, true, false))
|
||||||
|
{
|
||||||
|
return Some(*font);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do we have italic?
|
||||||
|
if let Some(font) = self
|
||||||
|
.fonts
|
||||||
|
.get(&FontDescriptor::from_parts(name, false, true))
|
||||||
|
{
|
||||||
|
return Some(*font);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_bold && !is_italic {
|
||||||
|
// Do we have bold italic? (unless we already looked for it)
|
||||||
|
if let Some(font) = self
|
||||||
|
.fonts
|
||||||
|
.get(&FontDescriptor::from_parts(name, true, true))
|
||||||
|
{
|
||||||
|
return Some(*font);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's no match at all, then it should not show anything...
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `Graphic` with the given character ID.
|
/// Returns the `Graphic` with the given character ID.
|
||||||
|
@ -527,7 +591,7 @@ impl<'gc> Library<'gc> {
|
||||||
match definition {
|
match definition {
|
||||||
FontDefinition::SwfTag(tag, encoding) => {
|
FontDefinition::SwfTag(tag, encoding) => {
|
||||||
let font = Font::from_swf_tag(gc_context, renderer, tag, encoding);
|
let font = Font::from_swf_tag(gc_context, renderer, tag, encoding);
|
||||||
let name = font.descriptor().class().to_owned();
|
let name = font.descriptor().name().to_owned();
|
||||||
info!("Loaded new device font \"{name}\" from swf tag");
|
info!("Loaded new device font \"{name}\" from swf tag");
|
||||||
self.device_fonts.insert(name, font);
|
self.device_fonts.insert(name, font);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package {
|
||||||
|
|
||||||
|
import flash.display.MovieClip;
|
||||||
|
import flash.text.TextField;
|
||||||
|
import flash.text.TextFormat;
|
||||||
|
|
||||||
|
|
||||||
|
public class Test extends MovieClip {
|
||||||
|
const NUM_ROWS: int = 20;
|
||||||
|
|
||||||
|
public function Test() {
|
||||||
|
// Bonus points: all the font names here are arbitrarily capitalised to show that equality is case insensitive
|
||||||
|
|
||||||
|
// SCP doesn't have Regular in this SWF, so see what that prefers to fall back to (spoiler: bold)
|
||||||
|
addTextField("Source Code Pro Regular", "Source code Pro", false, false, 0);
|
||||||
|
addTextField("Source Code Pro Bold", "Source code Pro", true, false, 1);
|
||||||
|
addTextField("Source Code Pro Italic", "Source code Pro", false, true, 2);
|
||||||
|
addTextField("Source Code Pro Bold Italic", "Source code Pro", true, true, 3);
|
||||||
|
|
||||||
|
// NS only has regular in this SWF, so show that they all are regular
|
||||||
|
addTextField("Noto Sans Regular", "Noto SANS", false, false, 4);
|
||||||
|
addTextField("Noto Sans Bold", "Noto SANS", true, false, 5);
|
||||||
|
addTextField("Noto Sans Italic", "Noto SANS", false, true, 6);
|
||||||
|
addTextField("Noto Sans Bold Italic", "Noto SANS", true, true, 7);
|
||||||
|
|
||||||
|
// UM NF doesn't have Regular or Bold in this SWF, so see what that prefers to fall back to (spoiler: italic)
|
||||||
|
addTextField("UbuntuMono NF Regular", "ubuntumono nf", false, false, 8);
|
||||||
|
addTextField("UbuntuMono NF Bold", "ubuntumono nf", true, false, 9);
|
||||||
|
addTextField("UbuntuMono NF Italic", "ubuntumono nf", false, true, 10);
|
||||||
|
addTextField("UbuntuMono NF Bold Italic", "ubuntumono nf", true, true, 11);
|
||||||
|
|
||||||
|
// JBM NF only has bold italic in this SWF, so show they are all bold italic
|
||||||
|
addTextField("JetBrainsMono NF Regular", "JETBRAINSMONO NF", false, false, 12);
|
||||||
|
addTextField("JetBrainsMono NF Bold", "JETBRAINSMONO NF", true, false, 13);
|
||||||
|
addTextField("JetBrainsMono NF Italic", "JETBRAINSMONO NF", false, true, 14);
|
||||||
|
addTextField("JetBrainsMono NF Bold Italic", "JETBRAINSMONO NF", true, true, 15);
|
||||||
|
|
||||||
|
// SUI doesn't have Regular or Italic, so see what the others fall back to (Bold & Bold Italic)
|
||||||
|
addTextField("Segoe UI Regular", "Segoe ui", false, false, 16);
|
||||||
|
addTextField("Segoe UI Bold", "Segoe ui", true, false, 17);
|
||||||
|
addTextField("Segoe UI Italic", "Segoe ui", false, true, 18);
|
||||||
|
addTextField("Segoe UI Bold Italic", "Segoe ui", true, true, 19);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTextField(text: String, font: String, bold: Boolean, italic: Boolean, y: int) {
|
||||||
|
var textField: TextField = new TextField();
|
||||||
|
var textFormat: TextFormat = new TextFormat();
|
||||||
|
textFormat.font = font;
|
||||||
|
textFormat.italic = italic;
|
||||||
|
textFormat.bold = bold;
|
||||||
|
textFormat.size = 30;
|
||||||
|
|
||||||
|
textField.defaultTextFormat = textFormat;
|
||||||
|
textField.embedFonts = true;
|
||||||
|
textField.text = text;
|
||||||
|
|
||||||
|
textField.y = Math.floor(stage.stageHeight / NUM_ROWS) * y;
|
||||||
|
textField.width = stage.stageWidth;
|
||||||
|
addChild(textField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,7 @@
|
||||||
|
num_frames = 1
|
||||||
|
|
||||||
|
[image_comparisons.output]
|
||||||
|
tolerance = 3
|
||||||
|
|
||||||
|
[player_options]
|
||||||
|
with_renderer = { optional = false, sample_count = 1 }
|
|
@ -0,0 +1,36 @@
|
||||||
|
package {
|
||||||
|
|
||||||
|
import flash.display.MovieClip;
|
||||||
|
import flash.text.TextField;
|
||||||
|
import flash.text.TextFormat;
|
||||||
|
|
||||||
|
|
||||||
|
public class Test extends MovieClip {
|
||||||
|
const NUM_ROWS: int = 4;
|
||||||
|
|
||||||
|
public function Test() {
|
||||||
|
addTextField("Source Code Pro Regular", "Source Code Pro", false, false, 0);
|
||||||
|
addTextField("Source Code Pro Bold", "Source Code Pro", true, false, 1);
|
||||||
|
addTextField("Source Code Pro Italic", "Source Code Pro", false, true, 2);
|
||||||
|
addTextField("Source Code Pro Bold Italic", "Source Code Pro", true, true, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTextField(text: String, font: String, bold: Boolean, italic: Boolean, y: int) {
|
||||||
|
var textField: TextField = new TextField();
|
||||||
|
var textFormat: TextFormat = new TextFormat();
|
||||||
|
textFormat.font = font;
|
||||||
|
textFormat.italic = italic;
|
||||||
|
textFormat.bold = bold;
|
||||||
|
textFormat.size = 30;
|
||||||
|
|
||||||
|
textField.defaultTextFormat = textFormat;
|
||||||
|
textField.embedFonts = true;
|
||||||
|
textField.text = text;
|
||||||
|
|
||||||
|
textField.y = Math.floor(stage.stageHeight / NUM_ROWS) * y;
|
||||||
|
textField.width = stage.stageWidth;
|
||||||
|
addChild(textField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,9 @@
|
||||||
|
# This test shows that it ignores missing glyphs when matching embedded fonts, choosing only on style and name
|
||||||
|
|
||||||
|
num_frames = 1
|
||||||
|
|
||||||
|
[image_comparisons.output]
|
||||||
|
tolerance = 3
|
||||||
|
|
||||||
|
[player_options]
|
||||||
|
with_renderer = { optional = false, sample_count = 1 }
|
|
@ -0,0 +1,36 @@
|
||||||
|
package {
|
||||||
|
|
||||||
|
import flash.display.MovieClip;
|
||||||
|
import flash.text.TextField;
|
||||||
|
import flash.text.TextFormat;
|
||||||
|
|
||||||
|
|
||||||
|
public class Test extends MovieClip {
|
||||||
|
const NUM_ROWS: int = 4;
|
||||||
|
|
||||||
|
public function Test() {
|
||||||
|
addTextField("Source Code Pro Regular", "Source Code Pro", false, false, 0);
|
||||||
|
addTextField("Source Code Pro Bold", "Source Code Pro", true, false, 1);
|
||||||
|
addTextField("Source Code Pro Italic", "Source Code Pro", false, true, 2);
|
||||||
|
addTextField("Source Code Pro Bold Italic", "Source Code Pro", true, true, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTextField(text: String, font: String, bold: Boolean, italic: Boolean, y: int) {
|
||||||
|
var textField: TextField = new TextField();
|
||||||
|
var textFormat: TextFormat = new TextFormat();
|
||||||
|
textFormat.font = font;
|
||||||
|
textFormat.italic = italic;
|
||||||
|
textFormat.bold = bold;
|
||||||
|
textFormat.size = 30;
|
||||||
|
|
||||||
|
textField.defaultTextFormat = textFormat;
|
||||||
|
textField.embedFonts = true;
|
||||||
|
textField.text = text;
|
||||||
|
|
||||||
|
textField.y = Math.floor(stage.stageHeight / NUM_ROWS) * y;
|
||||||
|
textField.width = stage.stageWidth;
|
||||||
|
addChild(textField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 5.4 KiB |
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,10 @@
|
||||||
|
# There are no fonts embedded in this swf. It should not render anything at all, or error.
|
||||||
|
|
||||||
|
num_frames = 1
|
||||||
|
known_failure = true # Right now we intentionally fall back, because we don't support DefineFont4 embedded fonts yet
|
||||||
|
|
||||||
|
[image_comparisons.output]
|
||||||
|
tolerance = 0
|
||||||
|
|
||||||
|
[player_options]
|
||||||
|
with_renderer = { optional = false, sample_count = 1 }
|
Loading…
Reference in New Issue