diff --git a/core/src/font.rs b/core/src/font.rs index 98705e1f0ac72..51335b0843f38 100644 --- a/core/src/font.rs +++ b/core/src/font.rs @@ -35,6 +35,20 @@ pub enum DefaultFont { JapaneseMincho, } +impl DefaultFont { + pub fn from_name(name: &str) -> Option { + Some(match name { + "_serif" => DefaultFont::Serif, + "_sans" => DefaultFont::Sans, + "_typewriter" => DefaultFont::Typewriter, + "_ゴシック" => DefaultFont::JapaneseGothic, + "_等幅" => DefaultFont::JapaneseGothicMono, + "_明朝" => DefaultFont::JapaneseMincho, + _ => return None, + }) + } +} + fn round_to_pixel(t: Twips) -> Twips { Twips::from_pixels(t.to_pixels().round()) } @@ -333,6 +347,12 @@ pub enum FontType { Device, } +impl FontType { + pub fn is_embedded(self) -> bool { + self != Self::Device + } +} + #[derive(Debug, Clone, Collect, Copy)] #[collect(no_drop)] pub struct Font<'gc>(Gc<'gc, FontData>); diff --git a/core/src/html/layout.rs b/core/src/html/layout.rs index 33be8b5cfd053..4d245adc2c973 100644 --- a/core/src/html/layout.rs +++ b/core/src/html/layout.rs @@ -13,7 +13,7 @@ use ruffle_render::shape_utils::DrawCommand; use std::cmp::{max, min, Ordering}; use std::fmt::{Debug, Formatter}; use std::mem; -use std::ops::{Deref, Range}; +use std::ops::Range; use std::slice::Iter; use std::sync::Arc; use swf::{Point, Rectangle, Twips}; @@ -646,7 +646,7 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> { // Note that the SWF can still contain a DefineFont tag with no glyphs/layout info in this case (see #451). // In an ideal world, device fonts would search for a matching font on the system and render it in some way. - if self.font_type != FontType::Device { + if self.font_type.is_embedded() { if let Some(font) = context .library .get_embedded_font_by_name( @@ -666,48 +666,51 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> { // return new_empty_font(context, span, self.font_type); } - // Check if the font name is one of the known default fonts. - if let Some(default_font) = match font_name.deref() { - "_serif" => Some(DefaultFont::Serif), - "_sans" => Some(DefaultFont::Sans), - "_typewriter" => Some(DefaultFont::Typewriter), - "_ゴシック" => Some(DefaultFont::JapaneseGothic), - "_等幅" => Some(DefaultFont::JapaneseGothicMono), - "_明朝" => Some(DefaultFont::JapaneseMincho), - _ => None, - } { - if let Some(&font) = context - .library - .default_font( - default_font, - span.style.bold, - span.style.italic, - context.ui, - context.renderer, - context.gc_context, - ) - .first() - { + // Specifying multiple font names is supported only for device fonts. + let font_names: Vec<&str> = font_name.split(",").collect(); + for font_name in &font_names { + let font_name = font_name.trim(); + + // Check if the font name is one of the known default fonts. + if let Some(default_font) = DefaultFont::from_name(font_name) { + if let Some(&font) = context + .library + .default_font( + default_font, + span.style.bold, + span.style.italic, + context.ui, + context.renderer, + context.gc_context, + ) + .first() + { + return font; + } else { + let font_desc = describe_font(span); + tracing::error!( + "Known default device font not found: {font_desc}, text will be missing" + ); + return new_empty_font(context, span, self.font_type); + } + } + + if let Some(font) = context.library.get_or_load_device_font( + font_name, + span.style.bold, + span.style.italic, + context.ui, + context.renderer, + context.gc_context, + ) { return font; - } else { - let font_desc = describe_font(span); - tracing::error!( - "Known default device font not found: {font_desc}, text will be missing" - ); - return new_empty_font(context, span, self.font_type); } } - if let Some(font) = context.library.get_or_load_device_font( - &font_name, - span.style.bold, - span.style.italic, - context.ui, - context.renderer, - context.gc_context, - ) { - return font; - } + // TODO We fall back to the default font based on the first font in the list. + // This is mainly to preserve old behavior, that might change when we + // implement a proper fallback. + let font_name = font_names.first().copied().unwrap_or(""); // TODO: handle multiple fonts for a definition, each covering different sets of glyphs @@ -716,7 +719,7 @@ impl<'a, 'gc> LayoutContext<'a, 'gc> { // well-known aliases for the default fonts for better compatibility // with devices that don't have those fonts installed. As a last resort // we fall back to using sans (like Flash). - let default_font = match font_name.deref() { + let default_font = match font_name { "Times New Roman" => DefaultFont::Serif, "Arial" => DefaultFont::Sans, "Consolas" => DefaultFont::Typewriter, diff --git a/tests/framework/src/backends/ui.rs b/tests/framework/src/backends/ui.rs index 4905098f1d080..371afd531bdfe 100644 --- a/tests/framework/src/backends/ui.rs +++ b/tests/framework/src/backends/ui.rs @@ -133,7 +133,10 @@ impl UiBackend for TestUiBackend { register: &mut dyn FnMut(FontDefinition), ) { for font in &self.fonts { - if font.family != name || font.bold != is_bold || font.italic != is_italic { + if !font.family.eq_ignore_ascii_case(name) + || font.bold != is_bold + || font.italic != is_italic + { continue; } diff --git a/tests/tests/swfs/fonts/device_font_list/Test.as b/tests/tests/swfs/fonts/device_font_list/Test.as new file mode 100644 index 0000000000000..32a190f192cdb --- /dev/null +++ b/tests/tests/swfs/fonts/device_font_list/Test.as @@ -0,0 +1,106 @@ +package { +import flash.display.*; +import flash.text.*; + +[SWF(width="100", height="200")] +public class Test extends Sprite { + [Embed(source="TestFontA.ttf", fontName="EmbeddedTestFontA", embedAsCFF="false", unicodeRange="U+0061-U+0064")] + private var EmbeddedTestFontA:Class; + + [Embed(source="TestFontB.ttf", fontName="EmbeddedTestFontB", embedAsCFF="false", unicodeRange="U+0061-U+0064")] + private var EmbeddedTestFontB:Class; + + private var nextY: Number = 0; + + private var fontListsDevice: Array = [ + "Totally Unknown, TestFontA , TestFontB", + " testFOntB , TestFontA , TestFontB", + ]; + private var fontListsEmbedded: Array = [ + "Totally Unknown, EmbeddedTestFontA, EmbeddedTestFontB", + "EmbeddedTestFontA", + " EmbeddedTestFontA", + "EmbeddedTestFontA ", + " EmbeddedTestFontA ", + " embeddedTESTFonta", + ]; + + public function Test() { + stage.scaleMode = "noScale"; + + for each (var embedded in [false, true]) { + var fontLists = embedded ? fontListsEmbedded : fontListsDevice; + for each (var fontList in fontLists) { + testFontListCss(embedded, fontList); + testFontListFormat(embedded, fontList); + } + } + } + + function testFontListCss(embedded: Boolean, fontList: String) { + trace("Testing CSS font list fallback:"); + trace(" Embedded? = " + embedded); + trace(" Font list? = " + fontList); + + var style: StyleSheet = new StyleSheet(); + + var classFontList:Object = new Object(); + classFontList.fontFamily = fontList; + classFontList.fontSize = 20; + style.setStyle(".fontlist", classFontList); + + var text: TextField = new TextField(); + text.embedFonts = embedded; + text.styleSheet = style; + + text.width = 100; + text.height = 50; + text.y = nextY; + nextY += text.height; + text.text = "abc"; + + addChild(text); + + traceChars(text); + } + + function testFontListFormat(embedded: Boolean, fontList: String) { + trace("Testing TextFormat font list fallback:"); + trace(" Embedded? = " + embedded); + trace(" Font list? = " + fontList); + + var tf: TextFormat = new TextFormat(fontList, 20); + var text: TextField = new TextField(); + text.embedFonts = embedded; + text.defaultTextFormat = tf; + + text.width = 100; + text.height = 50; + text.y = nextY; + nextY += text.height; + text.text = "abc"; + + addChild(text); + + traceChars(text); + } + + private function traceChars(text: TextField) { + traceChar(text, 0); + traceChar(text, 1); + traceChar(text, 2); + } + + private function traceChar(text: TextField, i: int) { + try { + var ch: Number = text.getCharBoundaries(i).width; + if (ch == 32) { + trace(" Char " + i + " is TestFontA"); + } + if (ch == 30) { + trace(" Char " + i + " is TestFontB"); + } + } catch(e) {} + } +} +} diff --git a/tests/tests/swfs/fonts/device_font_list/TestFontA.sfd b/tests/tests/swfs/fonts/device_font_list/TestFontA.sfd new file mode 100644 index 0000000000000..abd3c32494858 --- /dev/null +++ b/tests/tests/swfs/fonts/device_font_list/TestFontA.sfd @@ -0,0 +1,88 @@ +SplineFontDB: 3.2 +FontName: TestFontA +FullName: TestFontA +FamilyName: TestFontA +Weight: Regular +Copyright: Copyright (c) 2024, Kamil Jarosz +UComments: "2024-7-24: Created with FontForge (http://fontforge.org)" +Version: 001.000 +ItalicAngle: 0 +UnderlinePosition: -76 +UnderlineWidth: 38 +Ascent: 800 +Descent: 200 +InvalidEm: 0 +LayerCount: 2 +Layer: 0 0 "Back" 1 +Layer: 1 0 "Fore" 0 +XUID: [1021 253 198287149 6396829] +StyleMap: 0x0000 +FSType: 0 +OS2Version: 0 +OS2_WeightWidthSlopeOnly: 0 +OS2_UseTypoMetrics: 1 +CreationTime: 1721856925 +ModificationTime: 1737156144 +PfmFamily: 17 +TTFWeight: 400 +TTFWidth: 5 +LineGap: 100 +VLineGap: 0 +OS2TypoAscent: 0 +OS2TypoAOffset: 1 +OS2TypoDescent: 0 +OS2TypoDOffset: 1 +OS2TypoLinegap: 100 +OS2WinAscent: 0 +OS2WinAOffset: 1 +OS2WinDescent: 0 +OS2WinDOffset: 1 +HheadAscent: 0 +HheadAOffset: 1 +HheadDescent: 0 +HheadDOffset: 1 +OS2Vendor: 'PfEd' +MarkAttachClasses: 1 +DEI: 91125 +Encoding: ISO8859-1 +UnicodeInterp: none +NameList: AGL For New Fonts +DisplaySize: -48 +AntiAlias: 1 +FitToEm: 0 +WinInfo: 0 30 10 +BeginPrivate: 0 +EndPrivate +BeginChars: 256 2 + +StartChar: a +Encoding: 97 97 0 +Width: 1600 +Flags: HW +LayerCount: 2 +Fore +SplineSet +0 800 m 1 + 1600 800 l 1 + 1600 0 l 1 + 0 0 l 1 + 0 800 l 1 +EndSplineSet +EndChar + +StartChar: c +Encoding: 99 99 1 +Width: 1600 +Flags: HW +LayerCount: 2 +Fore +SplineSet +0 800 m 1 + 1600 800 l 1 + 1600 0 l 1 + 0 0 l 1 + 0 800 l 1 +EndSplineSet +EndChar +EndChars +EndSplineFont diff --git a/tests/tests/swfs/fonts/device_font_list/TestFontA.ttf b/tests/tests/swfs/fonts/device_font_list/TestFontA.ttf new file mode 100644 index 0000000000000..bc68e32831c67 Binary files /dev/null and b/tests/tests/swfs/fonts/device_font_list/TestFontA.ttf differ diff --git a/tests/tests/swfs/fonts/device_font_list/TestFontB.sfd b/tests/tests/swfs/fonts/device_font_list/TestFontB.sfd new file mode 100644 index 0000000000000..586e531b3d3a4 --- /dev/null +++ b/tests/tests/swfs/fonts/device_font_list/TestFontB.sfd @@ -0,0 +1,88 @@ +SplineFontDB: 3.2 +FontName: TestFontB +FullName: TestFontB +FamilyName: TestFontB +Weight: Regular +Copyright: Copyright (c) 2024, Kamil Jarosz +UComments: "2024-7-24: Created with FontForge (http://fontforge.org)" +Version: 001.000 +ItalicAngle: 0 +UnderlinePosition: -76 +UnderlineWidth: 38 +Ascent: 800 +Descent: 200 +InvalidEm: 0 +LayerCount: 2 +Layer: 0 0 "Back" 1 +Layer: 1 0 "Fore" 0 +XUID: [1021 253 198287149 6396829] +StyleMap: 0x0000 +FSType: 0 +OS2Version: 0 +OS2_WeightWidthSlopeOnly: 0 +OS2_UseTypoMetrics: 1 +CreationTime: 1721856925 +ModificationTime: 1737156185 +PfmFamily: 17 +TTFWeight: 400 +TTFWidth: 5 +LineGap: 100 +VLineGap: 0 +OS2TypoAscent: 0 +OS2TypoAOffset: 1 +OS2TypoDescent: 0 +OS2TypoDOffset: 1 +OS2TypoLinegap: 100 +OS2WinAscent: 0 +OS2WinAOffset: 1 +OS2WinDescent: 0 +OS2WinDOffset: 1 +HheadAscent: 0 +HheadAOffset: 1 +HheadDescent: 0 +HheadDOffset: 1 +OS2Vendor: 'PfEd' +MarkAttachClasses: 1 +DEI: 91125 +Encoding: ISO8859-1 +UnicodeInterp: none +NameList: AGL For New Fonts +DisplaySize: -48 +AntiAlias: 1 +FitToEm: 0 +WinInfo: 0 30 10 +BeginPrivate: 0 +EndPrivate +BeginChars: 256 2 + +StartChar: b +Encoding: 98 98 0 +Width: 1500 +Flags: HW +LayerCount: 2 +Fore +SplineSet +0 0 m 1 + 1500 0 l 5 + 1500 -200 l 5 + 0 -200 l 1 + 0 0 l 1 +EndSplineSet +EndChar + +StartChar: c +Encoding: 99 99 1 +Width: 1500 +Flags: HW +LayerCount: 2 +Fore +SplineSet +0 0 m 1 + 1500 0 l 5 + 1500 -200 l 5 + 0 -200 l 1 + 0 0 l 1 +EndSplineSet +EndChar +EndChars +EndSplineFont diff --git a/tests/tests/swfs/fonts/device_font_list/TestFontB.ttf b/tests/tests/swfs/fonts/device_font_list/TestFontB.ttf new file mode 100644 index 0000000000000..2a32521949690 Binary files /dev/null and b/tests/tests/swfs/fonts/device_font_list/TestFontB.ttf differ diff --git a/tests/tests/swfs/fonts/device_font_list/output.txt b/tests/tests/swfs/fonts/device_font_list/output.txt new file mode 100644 index 0000000000000..0735b72c6ae0d --- /dev/null +++ b/tests/tests/swfs/fonts/device_font_list/output.txt @@ -0,0 +1,64 @@ +Testing CSS font list fallback: + Embedded? = false + Font list? = Totally Unknown, TestFontA , TestFontB + Char 0 is TestFontA + Char 2 is TestFontA +Testing TextFormat font list fallback: + Embedded? = false + Font list? = Totally Unknown, TestFontA , TestFontB + Char 0 is TestFontA + Char 2 is TestFontA +Testing CSS font list fallback: + Embedded? = false + Font list? = testFOntB , TestFontA , TestFontB + Char 1 is TestFontB + Char 2 is TestFontB +Testing TextFormat font list fallback: + Embedded? = false + Font list? = testFOntB , TestFontA , TestFontB + Char 1 is TestFontB + Char 2 is TestFontB +Testing CSS font list fallback: + Embedded? = true + Font list? = Totally Unknown, EmbeddedTestFontA, EmbeddedTestFontB +Testing TextFormat font list fallback: + Embedded? = true + Font list? = Totally Unknown, EmbeddedTestFontA, EmbeddedTestFontB +Testing CSS font list fallback: + Embedded? = true + Font list? = EmbeddedTestFontA + Char 0 is TestFontA + Char 2 is TestFontA +Testing TextFormat font list fallback: + Embedded? = true + Font list? = EmbeddedTestFontA + Char 0 is TestFontA + Char 2 is TestFontA +Testing CSS font list fallback: + Embedded? = true + Font list? = EmbeddedTestFontA + Char 0 is TestFontA + Char 2 is TestFontA +Testing TextFormat font list fallback: + Embedded? = true + Font list? = EmbeddedTestFontA +Testing CSS font list fallback: + Embedded? = true + Font list? = EmbeddedTestFontA +Testing TextFormat font list fallback: + Embedded? = true + Font list? = EmbeddedTestFontA +Testing CSS font list fallback: + Embedded? = true + Font list? = EmbeddedTestFontA +Testing TextFormat font list fallback: + Embedded? = true + Font list? = EmbeddedTestFontA +Testing CSS font list fallback: + Embedded? = true + Font list? = embeddedTESTFonta + Char 0 is TestFontA + Char 2 is TestFontA +Testing TextFormat font list fallback: + Embedded? = true + Font list? = embeddedTESTFonta diff --git a/tests/tests/swfs/fonts/device_font_list/test.swf b/tests/tests/swfs/fonts/device_font_list/test.swf new file mode 100644 index 0000000000000..c2047980a853e Binary files /dev/null and b/tests/tests/swfs/fonts/device_font_list/test.swf differ diff --git a/tests/tests/swfs/fonts/device_font_list/test.toml b/tests/tests/swfs/fonts/device_font_list/test.toml new file mode 100644 index 0000000000000..fdd8414b30647 --- /dev/null +++ b/tests/tests/swfs/fonts/device_font_list/test.toml @@ -0,0 +1,9 @@ +num_ticks = 1 + +[fonts.a] +family = "TestFontA" +path = "TestFontA.ttf" + +[fonts.b] +family = "TestFontB" +path = "TestFontB.ttf"