diff options
Diffstat (limited to 'components/fonts/platform/windows')
-rw-r--r-- | components/fonts/platform/windows/font.rs | 276 | ||||
-rw-r--r-- | components/fonts/platform/windows/font_list.rs | 379 |
2 files changed, 655 insertions, 0 deletions
diff --git a/components/fonts/platform/windows/font.rs b/components/fonts/platform/windows/font.rs new file mode 100644 index 00000000000..8dd2d78bb1f --- /dev/null +++ b/components/fonts/platform/windows/font.rs @@ -0,0 +1,276 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// NOTE: https://www.chromium.org/directwrite-font-proxy has useful +// information for an approach that we'll likely need to take when the +// renderer moves to a sandboxed process. + +use std::cmp::{max, min}; +use std::fmt; +use std::io::Cursor; +use std::ops::Deref; +use std::sync::Arc; + +use app_units::Au; +use dwrote::{FontFace, FontFile}; +use log::{debug, warn}; +use style::computed_values::font_stretch::T as StyleFontStretch; +use style::computed_values::font_weight::T as StyleFontWeight; +use style::values::computed::font::FontStyle as StyleFontStyle; +use truetype::tables::WindowsMetrics; +use truetype::value::Read; +use webrender_api::FontInstanceFlags; + +use crate::{ + ot_tag, FontIdentifier, FontMetrics, FontTableMethods, FontTableTag, FontTemplateDescriptor, + FractionalPixel, GlyphId, PlatformFontMethods, +}; + +// 1em = 12pt = 16px, assuming 72 points per inch and 96 px per inch +fn pt_to_px(pt: f64) -> f64 { + pt / 72. * 96. +} +fn em_to_px(em: f64) -> f64 { + em * 16. +} +fn au_from_em(em: f64) -> Au { + Au::from_f64_px(em_to_px(em)) +} +fn au_from_pt(pt: f64) -> Au { + Au::from_f64_px(pt_to_px(pt)) +} + +pub struct FontTable { + data: Vec<u8>, +} + +impl FontTable { + pub fn wrap(data: &[u8]) -> FontTable { + FontTable { + data: data.to_vec(), + } + } +} + +impl FontTableMethods for FontTable { + fn buffer(&self) -> &[u8] { + &self.data + } +} + +#[derive(Debug)] +pub struct PlatformFont { + face: Nondebug<FontFace>, + /// A reference to this data used to create this [`PlatformFont`], ensuring the + /// data stays alive of the lifetime of this struct. + _data: Arc<Vec<u8>>, + em_size: f32, + du_to_px: f32, + scaled_du_to_px: f32, +} + +// Based on information from the Skia codebase, it seems that DirectWrite APIs from +// Windows 10 and beyond are thread safe. If problems arise from this, we can protect the +// platform font with a Mutex. +// See https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/src/ports/SkScalerContext_win_dw.cpp;l=56;bpv=0;bpt=1. +unsafe impl Sync for PlatformFont {} +unsafe impl Send for PlatformFont {} + +struct Nondebug<T>(T); + +impl<T> fmt::Debug for Nondebug<T> { + fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result { + Ok(()) + } +} + +impl<T> Deref for Nondebug<T> { + type Target = T; + fn deref(&self) -> &T { + &self.0 + } +} + +impl PlatformFontMethods for PlatformFont { + fn new_from_data( + _font_identifier: FontIdentifier, + data: Arc<Vec<u8>>, + face_index: u32, + pt_size: Option<Au>, + ) -> Result<Self, &'static str> { + let font_file = FontFile::new_from_data(data.clone()).ok_or("Could not create FontFile")?; + let face = font_file + .create_face(face_index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) + .map_err(|_| "Could not create FontFace")?; + + let pt_size = pt_size.unwrap_or(au_from_pt(12.)); + let du_per_em = face.metrics().metrics0().designUnitsPerEm as f32; + + let em_size = pt_size.to_f32_px() / 16.; + let design_units_per_pixel = du_per_em / 16.; + + let design_units_to_pixels = 1. / design_units_per_pixel; + let scaled_design_units_to_pixels = em_size / design_units_per_pixel; + + Ok(PlatformFont { + face: Nondebug(face), + _data: data, + em_size, + du_to_px: design_units_to_pixels, + scaled_du_to_px: scaled_design_units_to_pixels, + }) + } + + fn descriptor(&self) -> FontTemplateDescriptor { + // We need the font (DWriteFont) in order to be able to query things like + // the family name, face name, weight, etc. On Windows 10, the + // DWriteFontFace3 interface provides this on the FontFace, but that's only + // available on Win10+. + // + // Instead, we do the parsing work using the truetype crate for raw fonts. + // We're just extracting basic info, so this is sufficient for now. + // + // The `dwrote` APIs take SFNT table tags in a reversed byte order, which + // is why `u32::swap_bytes()` is called here. + let windows_metrics_bytes = self + .face + .get_font_table(u32::swap_bytes(ot_tag!('O', 'S', '/', '2'))); + if windows_metrics_bytes.is_none() { + warn!("Could not find OS/2 table in font."); + return FontTemplateDescriptor::default(); + } + + let mut cursor = Cursor::new(windows_metrics_bytes.as_ref().unwrap()); + let Ok(table) = WindowsMetrics::read(&mut cursor) else { + warn!("Could not read OS/2 table in font."); + return FontTemplateDescriptor::default(); + }; + + let (weight_val, width_val, italic_bool) = match table { + WindowsMetrics::Version0(ref m) => { + (m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1) + }, + WindowsMetrics::Version1(ref m) => { + (m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1) + }, + WindowsMetrics::Version2(ref m) | + WindowsMetrics::Version3(ref m) | + WindowsMetrics::Version4(ref m) => { + (m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1) + }, + WindowsMetrics::Version5(ref m) => { + (m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1) + }, + }; + + let weight = StyleFontWeight::from_float(weight_val as f32); + let stretch = match min(9, max(1, width_val)) { + 1 => StyleFontStretch::ULTRA_CONDENSED, + 2 => StyleFontStretch::EXTRA_CONDENSED, + 3 => StyleFontStretch::CONDENSED, + 4 => StyleFontStretch::SEMI_CONDENSED, + 5 => StyleFontStretch::NORMAL, + 6 => StyleFontStretch::SEMI_EXPANDED, + 7 => StyleFontStretch::EXPANDED, + 8 => StyleFontStretch::EXTRA_EXPANDED, + 9 => StyleFontStretch::ULTRA_CONDENSED, + _ => { + warn!("Unknown stretch size."); + StyleFontStretch::NORMAL + }, + }; + + let style = if italic_bool { + StyleFontStyle::ITALIC + } else { + StyleFontStyle::NORMAL + }; + + FontTemplateDescriptor::new(weight, stretch, style) + } + + fn glyph_index(&self, codepoint: char) -> Option<GlyphId> { + let glyph = self.face.get_glyph_indices(&[codepoint as u32])[0]; + if glyph == 0 { + return None; + } + Some(glyph as GlyphId) + } + + fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> { + if glyph == 0 { + return None; + } + + let gm = self.face.get_design_glyph_metrics(&[glyph as u16], false)[0]; + let f = (gm.advanceWidth as f32 * self.scaled_du_to_px) as FractionalPixel; + + Some(f) + } + + /// Can this font do basic horizontal LTR shaping without Harfbuzz? + fn can_do_fast_shaping(&self) -> bool { + // TODO copy CachedKernTable from the MacOS X implementation to + // somehwere global and use it here. We could also implement the + // IDirectWriteFontFace1 interface and use the glyph kerning pair + // methods there. + false + } + + fn glyph_h_kerning(&self, _: GlyphId, _: GlyphId) -> FractionalPixel { + 0.0 + } + + fn metrics(&self) -> FontMetrics { + let dm = self.face.metrics().metrics0(); + + let au_from_du = |du| -> Au { Au::from_f32_px(du as f32 * self.du_to_px) }; + let au_from_du_s = |du| -> Au { Au::from_f32_px(du as f32 * self.scaled_du_to_px) }; + + // anything that we calculate and don't just pull out of self.face.metrics + // is pulled out here for clarity + let leading = dm.ascent - dm.capHeight; + + let zero_horizontal_advance = self + .glyph_index('0') + .and_then(|idx| self.glyph_h_advance(idx)) + .map(Au::from_f64_px); + let ic_horizontal_advance = self + .glyph_index('\u{6C34}') + .and_then(|idx| self.glyph_h_advance(idx)) + .map(Au::from_f64_px); + + let metrics = FontMetrics { + underline_size: au_from_du(dm.underlineThickness as i32), + underline_offset: au_from_du_s(dm.underlinePosition as i32), + strikeout_size: au_from_du(dm.strikethroughThickness as i32), + strikeout_offset: au_from_du_s(dm.strikethroughPosition as i32), + leading: au_from_du_s(leading as i32), + x_height: au_from_du_s(dm.xHeight as i32), + em_size: au_from_em(self.em_size as f64), + ascent: au_from_du_s(dm.ascent as i32), + descent: au_from_du_s(dm.descent as i32), + max_advance: au_from_pt(0.0), // FIXME + average_advance: au_from_pt(0.0), // FIXME + line_gap: au_from_du_s((dm.ascent + dm.descent + dm.lineGap as u16) as i32), + zero_horizontal_advance, + ic_horizontal_advance, + }; + debug!("Font metrics (@{} pt): {:?}", self.em_size * 12., metrics); + metrics + } + + fn table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> { + // dwrote (and presumably the Windows APIs) accept a reversed version of the table + // tag bytes, which means that `u32::swap_bytes` must be called here in order to + // use a byte order compatible with the rest of Servo. + self.face + .get_font_table(u32::swap_bytes(tag)) + .map(|bytes| FontTable { data: bytes }) + } + + fn webrender_font_instance_flags(&self) -> FontInstanceFlags { + FontInstanceFlags::empty() + } +} diff --git a/components/fonts/platform/windows/font_list.rs b/components/fonts/platform/windows/font_list.rs new file mode 100644 index 00000000000..31c1d631160 --- /dev/null +++ b/components/fonts/platform/windows/font_list.rs @@ -0,0 +1,379 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::hash::Hash; +use std::sync::Arc; + +use base::text::{unicode_plane, UnicodeBlock, UnicodeBlockMethod}; +use dwrote::{Font, FontCollection, FontDescriptor, FontStretch, FontStyle}; +use malloc_size_of_derive::MallocSizeOf; +use serde::{Deserialize, Serialize}; +use style::values::computed::{FontStyle as StyleFontStyle, FontWeight as StyleFontWeight}; +use style::values::specified::font::FontStretchKeyword; + +use crate::{ + EmojiPresentationPreference, FallbackFontSelectionOptions, FontTemplate, FontTemplateDescriptor, +}; + +pub static SANS_SERIF_FONT_FAMILY: &str = "Arial"; + +pub fn system_default_family(_: &str) -> Option<String> { + Some("Verdana".to_owned()) +} + +pub fn for_each_available_family<F>(mut callback: F) +where + F: FnMut(String), +{ + let system_fc = FontCollection::system(); + for family in system_fc.families_iter() { + callback(family.name()); + } +} + +/// An identifier for a local font on a Windows system. +#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub struct LocalFontIdentifier { + /// The FontDescriptor of this font. + #[ignore_malloc_size_of = "dwrote does not support MallocSizeOf"] + pub font_descriptor: Arc<FontDescriptor>, +} + +impl LocalFontIdentifier { + pub fn index(&self) -> u32 { + FontCollection::system() + .get_font_from_descriptor(&self.font_descriptor) + .map_or(0, |font| font.create_font_face().get_index()) + } + + pub(crate) fn read_data_from_file(&self) -> Vec<u8> { + let font = FontCollection::system() + .get_font_from_descriptor(&self.font_descriptor) + .unwrap(); + let face = font.create_font_face(); + let files = face.get_files(); + assert!(!files.is_empty()); + files[0].get_font_file_bytes() + } +} + +impl Eq for LocalFontIdentifier {} + +impl Hash for LocalFontIdentifier { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.font_descriptor.family_name.hash(state); + self.font_descriptor.weight.to_u32().hash(state); + self.font_descriptor.stretch.to_u32().hash(state); + self.font_descriptor.style.to_u32().hash(state); + } +} + +pub fn for_each_variation<F>(family_name: &str, mut callback: F) +where + F: FnMut(FontTemplate), +{ + let system_fc = FontCollection::system(); + if let Some(family) = system_fc.get_font_family_by_name(family_name) { + let count = family.get_font_count(); + for i in 0..count { + let font = family.get_font(i); + let template_descriptor = (&font).into(); + let local_font_identifier = LocalFontIdentifier { + font_descriptor: Arc::new(font.to_descriptor()), + }; + callback(FontTemplate::new_for_local_font( + local_font_identifier, + template_descriptor, + )) + } + } +} + +// Based on gfxWindowsPlatform::GetCommonFallbackFonts() in Gecko +pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> { + let mut families = Vec::new(); + if options.presentation_preference == EmojiPresentationPreference::Emoji { + families.push("Segoe UI Emoji"); + } + + families.push("Arial"); + match unicode_plane(options.character) { + // https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane + 0 => { + if let Some(block) = options.character.block() { + match block { + UnicodeBlock::CyrillicSupplement | + UnicodeBlock::Armenian | + UnicodeBlock::Hebrew => { + families.push("Estrangelo Edessa"); + families.push("Cambria"); + }, + + UnicodeBlock::Arabic | UnicodeBlock::ArabicSupplement => { + families.push("Microsoft Uighur"); + }, + + UnicodeBlock::Syriac => { + families.push("Estrangelo Edessa"); + }, + + UnicodeBlock::Thaana => { + families.push("MV Boli"); + }, + + UnicodeBlock::NKo => { + families.push("Ebrima"); + }, + + UnicodeBlock::Devanagari | UnicodeBlock::Bengali => { + families.push("Nirmala UI"); + families.push("Utsaah"); + families.push("Aparajita"); + }, + + UnicodeBlock::Gurmukhi | + UnicodeBlock::Gujarati | + UnicodeBlock::Oriya | + UnicodeBlock::Tamil | + UnicodeBlock::Telugu | + UnicodeBlock::Kannada | + UnicodeBlock::Malayalam | + UnicodeBlock::Sinhala | + UnicodeBlock::Lepcha | + UnicodeBlock::OlChiki | + UnicodeBlock::CyrillicExtendedC | + UnicodeBlock::SundaneseSupplement | + UnicodeBlock::VedicExtensions => { + families.push("Nirmala UI"); + }, + + UnicodeBlock::Thai => { + families.push("Leelawadee UI"); + }, + + UnicodeBlock::Lao => { + families.push("Lao UI"); + }, + + UnicodeBlock::Myanmar | + UnicodeBlock::MyanmarExtendedA | + UnicodeBlock::MyanmarExtendedB => { + families.push("Myanmar Text"); + }, + + UnicodeBlock::HangulJamo | + UnicodeBlock::HangulJamoExtendedA | + UnicodeBlock::HangulSyllables | + UnicodeBlock::HangulJamoExtendedB | + UnicodeBlock::HangulCompatibilityJamo => { + families.push("Malgun Gothic"); + }, + + UnicodeBlock::Ethiopic | + UnicodeBlock::EthiopicSupplement | + UnicodeBlock::EthiopicExtended | + UnicodeBlock::EthiopicExtendedA => { + families.push("Nyala"); + }, + + UnicodeBlock::Cherokee => { + families.push("Plantagenet Cherokee"); + }, + + UnicodeBlock::UnifiedCanadianAboriginalSyllabics | + UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => { + families.push("Euphemia"); + families.push("Segoe UI"); + }, + + UnicodeBlock::Khmer | UnicodeBlock::KhmerSymbols => { + families.push("Khmer UI"); + families.push("Leelawadee UI"); + }, + + UnicodeBlock::Mongolian => { + families.push("Mongolian Baiti"); + }, + + UnicodeBlock::TaiLe => { + families.push("Microsoft Tai Le"); + }, + + UnicodeBlock::NewTaiLue => { + families.push("Microsoft New Tai Lue"); + }, + + UnicodeBlock::Buginese | + UnicodeBlock::TaiTham | + UnicodeBlock::CombiningDiacriticalMarksExtended => { + families.push("Leelawadee UI"); + }, + + UnicodeBlock::GeneralPunctuation | + UnicodeBlock::SuperscriptsandSubscripts | + UnicodeBlock::CurrencySymbols | + UnicodeBlock::CombiningDiacriticalMarksforSymbols | + UnicodeBlock::LetterlikeSymbols | + UnicodeBlock::NumberForms | + UnicodeBlock::Arrows | + UnicodeBlock::MathematicalOperators | + UnicodeBlock::MiscellaneousTechnical | + UnicodeBlock::ControlPictures | + UnicodeBlock::OpticalCharacterRecognition | + UnicodeBlock::EnclosedAlphanumerics | + UnicodeBlock::BoxDrawing | + UnicodeBlock::BlockElements | + UnicodeBlock::GeometricShapes | + UnicodeBlock::MiscellaneousSymbols | + UnicodeBlock::Dingbats | + UnicodeBlock::MiscellaneousMathematicalSymbolsA | + UnicodeBlock::SupplementalArrowsA | + UnicodeBlock::SupplementalArrowsB | + UnicodeBlock::MiscellaneousMathematicalSymbolsB | + UnicodeBlock::SupplementalMathematicalOperators | + UnicodeBlock::MiscellaneousSymbolsandArrows | + UnicodeBlock::Glagolitic | + UnicodeBlock::LatinExtendedC | + UnicodeBlock::Coptic => { + families.push("Segoe UI"); + families.push("Segoe UI Symbol"); + families.push("Cambria"); + families.push("Meiryo"); + families.push("Lucida Sans Unicode"); + families.push("Ebrima"); + }, + + UnicodeBlock::GeorgianSupplement | + UnicodeBlock::Tifinagh | + UnicodeBlock::CyrillicExtendedA | + UnicodeBlock::SupplementalPunctuation | + UnicodeBlock::CJKRadicalsSupplement | + UnicodeBlock::KangxiRadicals | + UnicodeBlock::IdeographicDescriptionCharacters => { + families.push("Segoe UI"); + families.push("Segoe UI Symbol"); + families.push("Meiryo"); + }, + + UnicodeBlock::BraillePatterns => { + families.push("Segoe UI Symbol"); + }, + + UnicodeBlock::CJKSymbolsandPunctuation | + UnicodeBlock::Hiragana | + UnicodeBlock::Katakana | + UnicodeBlock::Bopomofo | + UnicodeBlock::Kanbun | + UnicodeBlock::BopomofoExtended | + UnicodeBlock::CJKStrokes | + UnicodeBlock::KatakanaPhoneticExtensions | + UnicodeBlock::CJKUnifiedIdeographs => { + families.push("Microsoft YaHei"); + families.push("Yu Gothic"); + }, + + UnicodeBlock::EnclosedCJKLettersandMonths => { + families.push("Malgun Gothic"); + }, + + UnicodeBlock::YijingHexagramSymbols => { + families.push("Segoe UI Symbol"); + }, + + UnicodeBlock::YiSyllables | UnicodeBlock::YiRadicals => { + families.push("Microsoft Yi Baiti"); + families.push("Segoe UI"); + }, + + UnicodeBlock::Vai | + UnicodeBlock::CyrillicExtendedB | + UnicodeBlock::Bamum | + UnicodeBlock::ModifierToneLetters | + UnicodeBlock::LatinExtendedD => { + families.push("Ebrima"); + families.push("Segoe UI"); + families.push("Cambria Math"); + }, + + UnicodeBlock::SylotiNagri | + UnicodeBlock::CommonIndicNumberForms | + UnicodeBlock::Phagspa | + UnicodeBlock::Saurashtra | + UnicodeBlock::DevanagariExtended => { + families.push("Microsoft PhagsPa"); + families.push("Nirmala UI"); + }, + + UnicodeBlock::KayahLi | UnicodeBlock::Rejang | UnicodeBlock::Javanese => { + families.push("Malgun Gothic"); + families.push("Javanese Text"); + families.push("Leelawadee UI"); + }, + + UnicodeBlock::AlphabeticPresentationForms => { + families.push("Microsoft Uighur"); + families.push("Gabriola"); + families.push("Sylfaen"); + }, + + UnicodeBlock::ArabicPresentationFormsA | + UnicodeBlock::ArabicPresentationFormsB => { + families.push("Traditional Arabic"); + families.push("Arabic Typesetting"); + }, + + UnicodeBlock::VariationSelectors | + UnicodeBlock::VerticalForms | + UnicodeBlock::CombiningHalfMarks | + UnicodeBlock::CJKCompatibilityForms | + UnicodeBlock::SmallFormVariants | + UnicodeBlock::HalfwidthandFullwidthForms | + UnicodeBlock::Specials => { + families.push("Microsoft JhengHei"); + }, + + _ => {}, + } + } + }, + + // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane + 1 => { + families.push("Segoe UI Symbol"); + families.push("Ebrima"); + families.push("Nirmala UI"); + families.push("Cambria Math"); + }, + + _ => {}, + } + + families.push("Arial Unicode MS"); + families +} + +impl From<&Font> for FontTemplateDescriptor { + fn from(font: &Font) -> Self { + let style = match font.style() { + FontStyle::Normal => StyleFontStyle::NORMAL, + FontStyle::Oblique => StyleFontStyle::OBLIQUE, + FontStyle::Italic => StyleFontStyle::ITALIC, + }; + let weight = StyleFontWeight::from_float(font.weight().to_u32() as f32); + let stretch = match font.stretch() { + FontStretch::Undefined => FontStretchKeyword::Normal, + FontStretch::UltraCondensed => FontStretchKeyword::UltraCondensed, + FontStretch::ExtraCondensed => FontStretchKeyword::ExtraCondensed, + FontStretch::Condensed => FontStretchKeyword::Condensed, + FontStretch::SemiCondensed => FontStretchKeyword::SemiCondensed, + FontStretch::Normal => FontStretchKeyword::Normal, + FontStretch::SemiExpanded => FontStretchKeyword::SemiExpanded, + FontStretch::Expanded => FontStretchKeyword::Expanded, + FontStretch::ExtraExpanded => FontStretchKeyword::ExtraExpanded, + FontStretch::UltraExpanded => FontStretchKeyword::UltraExpanded, + } + .compute(); + FontTemplateDescriptor::new(weight, stretch, style) + } +} |