/* 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 http://mozilla.org/MPL/2.0/. */ use app_units::Au; use euclid::{Point2D, Rect, Size2D}; use font_template::FontTemplateDescriptor; use platform::font::{FontHandle, FontTable}; use platform::font_context::FontContextHandle; use platform::font_template::FontTemplateData; use smallvec::SmallVec; use std::borrow::ToOwned; use std::cell::RefCell; use std::rc::Rc; use std::str; use std::sync::Arc; use style::computed_values::{font_stretch, font_variant, font_weight}; use style::properties::style_structs::Font as FontStyle; use text::Shaper; use text::glyph::{GlyphId, GlyphStore}; use text::shaping::ShaperMethods; use unicode_script::Script; use util::cache::HashCache; // 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) -> String; fn face_name(&self) -> String; fn is_italic(&self) -> bool; 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, GlyphId, GlyphId) -> FractionalPixel; fn metrics(&self) -> FontMetrics; fn table_for_tag(&self, FontTableTag) -> Option>; } // Used to abstract over the shaper's choice of fixed int representation. pub type FractionalPixel = f64; pub type FontTableTag = u32; pub 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 with_buffer(&self, F) where F: FnOnce(*const u8, usize); } #[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, } pub type SpecifiedFontStyle = FontStyle; pub struct Font { pub handle: FontHandle, pub metrics: FontMetrics, pub variant: font_variant::T, pub descriptor: FontTemplateDescriptor, pub requested_pt_size: Au, pub actual_pt_size: Au, pub shaper: Option, pub shape_cache: HashCache>, pub glyph_advance_cache: HashCache, } bitflags! { flags ShapingFlags: u8 { #[doc = "Set if the text is entirely whitespace."] const IS_WHITESPACE_SHAPING_FLAG = 0x01, #[doc = "Set if we are to ignore ligatures."] const IGNORE_LIGATURES_SHAPING_FLAG = 0x02, #[doc = "Set if we are to disable kerning."] const DISABLE_KERNING_SHAPING_FLAG = 0x04, #[doc = "Text direction is right-to-left."] const RTL_FLAG = 0x08, } } /// Various options that control text shaping. #[derive(Clone, Eq, PartialEq, Hash, Copy)] 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, Eq, PartialEq, Hash)] pub struct ShapeCacheEntry { text: String, options: ShapingOptions, } impl Font { pub fn shape_text(&mut self, text: &str, options: &ShapingOptions) -> Arc { self.make_shaper(options); //FIXME: find the equivalent of Equiv and the old ShapeCacheEntryRef let shaper = &self.shaper; let lookup_key = ShapeCacheEntry { text: text.to_owned(), options: options.clone(), }; if let Some(glyphs) = self.shape_cache.find(&lookup_key) { return glyphs.clone(); } let mut glyphs = GlyphStore::new(text.chars().count(), options.flags.contains(IS_WHITESPACE_SHAPING_FLAG), options.flags.contains(RTL_FLAG)); shaper.as_ref().unwrap().shape_text(text, options, &mut glyphs); let glyphs = Arc::new(glyphs); self.shape_cache.insert(ShapeCacheEntry { text: text.to_owned(), options: *options, }, glyphs.clone()); glyphs } fn make_shaper<'a>(&'a mut self, options: &ShapingOptions) -> &'a Shaper { // fast path: already created a shaper if let Some(ref mut shaper) = self.shaper { shaper.set_options(options); return shaper } let shaper = Shaper::new(self, options); self.shaper = Some(shaper); self.shaper.as_ref().unwrap() } 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(), self.handle.face_name()); result } #[inline] pub fn glyph_index(&self, codepoint: char) -> Option { let codepoint = match self.variant { font_variant::T::small_caps => codepoint.to_uppercase().next().unwrap(), //FIXME: #5938 font_variant::T::normal => codepoint, }; self.handle.glyph_index(codepoint) } pub fn glyph_h_kerning(&mut self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel { self.handle.glyph_h_kerning(first_glyph, second_glyph) } pub fn glyph_h_advance(&mut self, glyph: GlyphId) -> FractionalPixel { let handle = &self.handle; self.glyph_advance_cache.find_or_create(&glyph, |glyph| { match handle.glyph_h_advance(*glyph) { Some(adv) => adv, None => 10f64 as FractionalPixel // FIXME: Need fallback strategy } }) } } pub struct FontGroup { pub fonts: SmallVec<[Rc>; 8]>, } impl FontGroup { pub fn new(fonts: SmallVec<[Rc>; 8]>) -> FontGroup { FontGroup { fonts: fonts, } } } 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: ascent, descent: descent, } } }