/* 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 crate::font_context::{FontContext, FontSource}; use crate::font_template::FontTemplateDescriptor; use crate::platform::font::{FontHandle, FontTable}; use crate::platform::font_context::FontContextHandle; pub use crate::platform::font_list::fallback_font_families; use crate::platform::font_template::FontTemplateData; use crate::text::glyph::{ByteIndex, GlyphData, GlyphId, GlyphStore}; use crate::text::shaping::ShaperMethods; use crate::text::Shaper; use app_units::Au; use bitflags::bitflags; use euclid::default::{Point2D, Rect, Size2D}; use log::debug; use serde::{Deserialize, Serialize}; use servo_atoms::{atom, Atom}; use smallvec::SmallVec; use std::borrow::ToOwned; use std::cell::RefCell; use std::collections::HashMap; use std::iter; use std::rc::Rc; use std::str; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use style::computed_values::{font_stretch, font_style, font_variant_caps, font_weight}; use style::properties::style_structs::Font as FontStyleStruct; use style::values::computed::font::{GenericFontFamily, SingleFontFamily}; use unicode_script::Script; use webrender_api::FontInstanceKey; #[macro_export] macro_rules! ot_tag { ($t1:expr, $t2:expr, $t3:expr, $t4:expr) => { (($t1 as u32) << 24) | (($t2 as u32) << 16) | (($t3 as u32) << 8) | ($t4 as u32) }; } pub const GPOS: u32 = ot_tag!('G', 'P', 'O', 'S'); pub const GSUB: u32 = ot_tag!('G', 'S', 'U', 'B'); pub const KERN: u32 = ot_tag!('k', 'e', 'r', 'n'); pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0; static TEXT_SHAPING_PERFORMANCE_COUNTER: AtomicUsize = AtomicUsize::new(0); // FontHandle encapsulates access to the platform's font API, // e.g. quartz, FreeType. It provides access to metrics and tables // needed by the text shaper as well as access to the underlying font // resources needed by the graphics layer to draw glyphs. pub trait FontHandleMethods: Sized { fn new_from_template( fctx: &FontContextHandle, template: Arc, pt_size: Option, ) -> Result; fn template(&self) -> Arc; fn family_name(&self) -> Option; fn face_name(&self) -> Option; fn style(&self) -> font_style::T; fn boldness(&self) -> font_weight::T; fn stretchiness(&self) -> font_stretch::T; fn glyph_index(&self, codepoint: char) -> Option; fn glyph_h_advance(&self, _: GlyphId) -> Option; fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel; /// Can this font do basic horizontal LTR shaping without Harfbuzz? fn can_do_fast_shaping(&self) -> bool; fn metrics(&self) -> FontMetrics; fn table_for_tag(&self, _: FontTableTag) -> Option; /// A unique identifier for the font, allowing comparison. fn identifier(&self) -> Atom; } // Used to abstract over the shaper's choice of fixed int representation. pub type FractionalPixel = f64; pub type FontTableTag = u32; trait FontTableTagConversions { fn tag_to_str(&self) -> String; } impl FontTableTagConversions for FontTableTag { fn tag_to_str(&self) -> String { let bytes = [ (self >> 24) as u8, (self >> 16) as u8, (self >> 8) as u8, (self >> 0) as u8, ]; str::from_utf8(&bytes).unwrap().to_owned() } } pub trait FontTableMethods { fn buffer(&self) -> &[u8]; } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct FontMetrics { pub underline_size: Au, pub underline_offset: Au, pub strikeout_size: Au, pub strikeout_offset: Au, pub leading: Au, pub x_height: Au, pub em_size: Au, pub ascent: Au, pub descent: Au, pub max_advance: Au, pub average_advance: Au, pub line_gap: Au, } /// `FontDescriptor` describes the parameters of a `Font`. It represents rendering a given font /// template at a particular size, with a particular font-variant-caps applied, etc. This contrasts /// with `FontTemplateDescriptor` in that the latter represents only the parameters inherent in the /// font data (weight, stretch, etc.). #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct FontDescriptor { pub template_descriptor: FontTemplateDescriptor, pub variant: font_variant_caps::T, pub pt_size: Au, } impl<'a> From<&'a FontStyleStruct> for FontDescriptor { fn from(style: &'a FontStyleStruct) -> Self { FontDescriptor { template_descriptor: FontTemplateDescriptor::from(style), variant: style.font_variant_caps, pt_size: Au::from_f32_px(style.font_size.size().px()), } } } #[derive(Debug)] pub struct Font { pub handle: FontHandle, pub metrics: FontMetrics, pub descriptor: FontDescriptor, pub actual_pt_size: Au, shaper: Option, shape_cache: RefCell>>, glyph_advance_cache: RefCell>, pub font_key: FontInstanceKey, } impl Font { pub fn new( handle: FontHandle, descriptor: FontDescriptor, actual_pt_size: Au, font_key: FontInstanceKey, ) -> Font { let metrics = handle.metrics(); Font { handle: handle, shaper: None, descriptor, actual_pt_size, metrics, shape_cache: RefCell::new(HashMap::new()), glyph_advance_cache: RefCell::new(HashMap::new()), font_key, } } /// A unique identifier for the font, allowing comparison. pub fn identifier(&self) -> Atom { self.handle.identifier() } } bitflags! { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct ShapingFlags: u8 { /// Set if the text is entirely whitespace. const IS_WHITESPACE_SHAPING_FLAG = 0x01; /// Set if we are to ignore ligatures. const IGNORE_LIGATURES_SHAPING_FLAG = 0x02; /// Set if we are to disable kerning. const DISABLE_KERNING_SHAPING_FLAG = 0x04; /// Text direction is right-to-left. const RTL_FLAG = 0x08; /// Set if word-break is set to keep-all. const KEEP_ALL_FLAG = 0x10; } } /// Various options that control text shaping. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct ShapingOptions { /// Spacing to add between each letter. Corresponds to the CSS 2.1 `letter-spacing` property. /// NB: You will probably want to set the `IGNORE_LIGATURES_SHAPING_FLAG` if this is non-null. pub letter_spacing: Option, /// Spacing to add between each word. Corresponds to the CSS 2.1 `word-spacing` property. pub word_spacing: Au, /// The Unicode script property of the characters in this run. pub script: Script, /// Various flags. pub flags: ShapingFlags, } /// An entry in the shape cache. #[derive(Clone, Debug, Eq, Hash, PartialEq)] struct ShapeCacheEntry { text: String, options: ShapingOptions, } impl Font { pub fn shape_text(&mut self, text: &str, options: &ShapingOptions) -> Arc { let this = self as *const Font; let mut shaper = self.shaper.take(); let lookup_key = ShapeCacheEntry { text: text.to_owned(), options: *options, }; let result = self .shape_cache .borrow_mut() .entry(lookup_key) .or_insert_with(|| { let start_time = time::precise_time_ns(); let mut glyphs = GlyphStore::new( text.len(), options .flags .contains(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG), options.flags.contains(ShapingFlags::RTL_FLAG), ); if self.can_do_fast_shaping(text, options) { debug!("shape_text: Using ASCII fast path."); self.shape_text_fast(text, options, &mut glyphs); } else { debug!("shape_text: Using Harfbuzz."); if shaper.is_none() { shaper = Some(Shaper::new(this)); } shaper .as_ref() .unwrap() .shape_text(text, options, &mut glyphs); } let end_time = time::precise_time_ns(); TEXT_SHAPING_PERFORMANCE_COUNTER .fetch_add((end_time - start_time) as usize, Ordering::Relaxed); Arc::new(glyphs) }) .clone(); self.shaper = shaper; result } fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool { options.script == Script::Latin && !options.flags.contains(ShapingFlags::RTL_FLAG) && self.handle.can_do_fast_shaping() && text.is_ascii() } /// Fast path for ASCII text that only needs simple horizontal LTR kerning. fn shape_text_fast(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) { let mut prev_glyph_id = None; for (i, byte) in text.bytes().enumerate() { let character = byte as char; let glyph_id = match self.glyph_index(character) { Some(id) => id, None => continue, }; let mut advance = Au::from_f64_px(self.glyph_h_advance(glyph_id)); if character == ' ' { // https://drafts.csswg.org/css-text-3/#word-spacing-property advance += options.word_spacing; } if let Some(letter_spacing) = options.letter_spacing { advance += letter_spacing; } let offset = prev_glyph_id.map(|prev| { let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id)); advance += h_kerning; Point2D::new(h_kerning, Au(0)) }); let glyph = GlyphData::new(glyph_id, advance, offset, true, true); glyphs.add_glyph_for_byte_index(ByteIndex(i as isize), character, &glyph); prev_glyph_id = Some(glyph_id); } glyphs.finalize_changes(); } pub fn table_for_tag(&self, tag: FontTableTag) -> Option { let result = self.handle.table_for_tag(tag); let status = if result.is_some() { "Found" } else { "Didn't find" }; debug!( "{} font table[{}] with family={}, face={}", status, tag.tag_to_str(), self.handle .family_name() .unwrap_or("unavailable".to_owned()), self.handle.face_name().unwrap_or("unavailable".to_owned()) ); result } #[inline] pub fn glyph_index(&self, codepoint: char) -> Option { let codepoint = match self.descriptor.variant { font_variant_caps::T::SmallCaps => codepoint.to_uppercase().next().unwrap(), //FIXME: #5938 font_variant_caps::T::Normal => codepoint, }; self.handle.glyph_index(codepoint) } pub fn has_glyph_for(&self, codepoint: char) -> bool { self.glyph_index(codepoint).is_some() } pub fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel { self.handle.glyph_h_kerning(first_glyph, second_glyph) } pub fn glyph_h_advance(&self, glyph: GlyphId) -> FractionalPixel { *self .glyph_advance_cache .borrow_mut() .entry(glyph) .or_insert_with(|| { match self.handle.glyph_h_advance(glyph) { Some(adv) => adv, None => LAST_RESORT_GLYPH_ADVANCE as FractionalPixel, // FIXME: Need fallback strategy } }) } } pub type FontRef = Rc>; /// A `FontGroup` is a prioritised list of fonts for a given set of font styles. It is used by /// `TextRun` to decide which font to render a character with. If none of the fonts listed in the /// styles are suitable, a fallback font may be used. #[derive(Debug)] pub struct FontGroup { descriptor: FontDescriptor, families: SmallVec<[FontGroupFamily; 8]>, last_matching_fallback: Option, } impl FontGroup { pub fn new(style: &FontStyleStruct) -> FontGroup { let descriptor = FontDescriptor::from(style); let families = style .font_family .families .iter() .map(|family| FontGroupFamily::new(descriptor.clone(), &family)) .collect(); FontGroup { descriptor, families, last_matching_fallback: None, } } /// Finds the first font, or else the first fallback font, which contains a glyph for /// `codepoint`. If no such font is found, returns the first available font or fallback font /// (which will cause a "glyph not found" character to be rendered). If no font at all can be /// found, returns None. pub fn find_by_codepoint( &mut self, mut font_context: &mut FontContext, codepoint: char, ) -> Option { let has_glyph = |font: &FontRef| font.borrow().has_glyph_for(codepoint); let font = self.find(&mut font_context, |font| has_glyph(font)); if font.is_some() { return font; } if let Some(ref fallback) = self.last_matching_fallback { if has_glyph(&fallback) { return self.last_matching_fallback.clone(); } } let font = self.find_fallback(&mut font_context, Some(codepoint), has_glyph); if font.is_some() { self.last_matching_fallback = font.clone(); return font; } self.first(&mut font_context) } /// Find the first available font in the group, or the first available fallback font. pub fn first( &mut self, mut font_context: &mut FontContext, ) -> Option { self.find(&mut font_context, |_| true) .or_else(|| self.find_fallback(&mut font_context, None, |_| true)) } /// Find a font which returns true for `predicate`. This method mutates because we may need to /// load new font data in the process of finding a suitable font. fn find(&mut self, mut font_context: &mut FontContext, predicate: P) -> Option where S: FontSource, P: FnMut(&FontRef) -> bool, { self.families .iter_mut() .filter_map(|family| family.font(&mut font_context)) .find(predicate) } /// Attempts to find a suitable fallback font which matches the `predicate`. The default /// family (i.e. "serif") will be tried first, followed by platform-specific family names. /// If a `codepoint` is provided, then its Unicode block may be used to refine the list of /// family names which will be tried. fn find_fallback( &mut self, font_context: &mut FontContext, codepoint: Option, predicate: P, ) -> Option where S: FontSource, P: FnMut(&FontRef) -> bool, { iter::once(FontFamilyDescriptor::default()) .chain(fallback_font_families(codepoint).into_iter().map(|family| { FontFamilyDescriptor::new(FontFamilyName::from(family), FontSearchScope::Local) })) .filter_map(|family| font_context.font(&self.descriptor, &family)) .find(predicate) } } /// A `FontGroupFamily` is a single font family in a `FontGroup`. It corresponds to one of the /// families listed in the `font-family` CSS property. The corresponding font data is lazy-loaded, /// only if actually needed. #[derive(Debug)] struct FontGroupFamily { font_descriptor: FontDescriptor, family_descriptor: FontFamilyDescriptor, loaded: bool, font: Option, } impl FontGroupFamily { fn new(font_descriptor: FontDescriptor, family: &SingleFontFamily) -> FontGroupFamily { let family_descriptor = FontFamilyDescriptor::new(FontFamilyName::from(family), FontSearchScope::Any); FontGroupFamily { font_descriptor, family_descriptor, loaded: false, font: None, } } /// Returns the font within this family which matches the style. We'll fetch the data from the /// `FontContext` the first time this method is called, and return a cached reference on /// subsequent calls. fn font(&mut self, font_context: &mut FontContext) -> Option { if !self.loaded { self.font = font_context.font(&self.font_descriptor, &self.family_descriptor); self.loaded = true; } self.font.clone() } } pub struct RunMetrics { // may be negative due to negative width (i.e., kerning of '.' in 'P.T.') pub advance_width: Au, pub ascent: Au, // nonzero pub descent: Au, // nonzero // this bounding box is relative to the left origin baseline. // so, bounding_box.position.y = -ascent pub bounding_box: Rect, } impl RunMetrics { pub fn new(advance: Au, ascent: Au, descent: Au) -> RunMetrics { let bounds = Rect::new( Point2D::new(Au(0), -ascent), Size2D::new(advance, ascent + descent), ); // TODO(Issue #125): support loose and tight bounding boxes; using the // ascent+descent and advance is sometimes too generous and // looking at actual glyph extents can yield a tighter box. RunMetrics { advance_width: advance, bounding_box: bounds, ascent, descent, } } } pub fn get_and_reset_text_shaping_performance_counter() -> usize { let value = TEXT_SHAPING_PERFORMANCE_COUNTER.swap(0, Ordering::SeqCst); value } /// The scope within which we will look for a font. #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub enum FontSearchScope { /// All fonts will be searched, including those specified via `@font-face` rules. Any, /// Only local system fonts will be searched. Local, } /// A font family name used in font selection. #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub enum FontFamilyName { /// A specific name such as `"Arial"` Specific(Atom), /// A generic name such as `sans-serif` Generic(Atom), } impl FontFamilyName { pub fn name(&self) -> &str { match *self { FontFamilyName::Specific(ref name) => name, FontFamilyName::Generic(ref name) => name, } } } impl<'a> From<&'a SingleFontFamily> for FontFamilyName { fn from(other: &'a SingleFontFamily) -> FontFamilyName { match *other { SingleFontFamily::FamilyName(ref family_name) => { FontFamilyName::Specific(family_name.name.clone()) }, SingleFontFamily::Generic(generic) => FontFamilyName::Generic(match generic { GenericFontFamily::None => panic!("Shouldn't appear in style"), GenericFontFamily::Serif => atom!("serif"), GenericFontFamily::SansSerif => atom!("sans-serif"), GenericFontFamily::Monospace => atom!("monospace"), GenericFontFamily::Cursive => atom!("cursive"), GenericFontFamily::Fantasy => atom!("fantasy"), GenericFontFamily::SystemUi => atom!("system-ui"), }), } } } impl<'a> From<&'a str> for FontFamilyName { fn from(other: &'a str) -> FontFamilyName { FontFamilyName::Specific(Atom::from(other)) } } /// The font family parameters for font selection. #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct FontFamilyDescriptor { pub name: FontFamilyName, pub scope: FontSearchScope, } impl FontFamilyDescriptor { pub fn new(name: FontFamilyName, scope: FontSearchScope) -> FontFamilyDescriptor { FontFamilyDescriptor { name, scope } } fn default() -> FontFamilyDescriptor { FontFamilyDescriptor { name: FontFamilyName::Generic(atom!("serif")), scope: FontSearchScope::Local, } } pub fn name(&self) -> &str { self.name.name() } }