/* 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 geom::{Point2D, Rect, Size2D}; use std::borrow::ToOwned; use std::mem; use std::slice; use std::rc::Rc; use std::cell::RefCell; use util::cache::HashCache; use util::smallvec::{SmallVec, SmallVec8}; use style::computed_values::{font_stretch, font_variant, font_weight}; use style::properties::style_structs::Font as FontStyle; use std::sync::Arc; use platform::font_context::FontContextHandle; use platform::font::{FontHandle, FontTable}; use util::geometry::Au; use text::glyph::{GlyphStore, GlyphId}; use text::shaping::ShaperMethods; use text::{Shaper, TextRun}; use font_template::FontTemplateDescriptor; use platform::font_template::FontTemplateData; // 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 { fn new_from_template(fctx: &FontContextHandle, template: Arc, pt_size: Option) -> Result; fn get_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 get_metrics(&self) -> FontMetrics; fn get_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 { unsafe { let pointer = mem::transmute::<&u32, *const u8>(self); let mut bytes = slice::from_raw_parts(pointer, 4).to_vec(); bytes.reverse(); String::from_utf8_unchecked(bytes) } } } pub trait FontTableMethods { fn with_buffer(&self, F) where F: FnOnce(*const u8, uint); } #[derive(Clone, Debug)] 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 type UsedFontStyle = 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 } } /// 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, /// 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(), }; match self.shape_cache.find(&lookup_key) { None => {} Some(glyphs) => return glyphs.clone(), } let mut glyphs = GlyphStore::new(text.chars().count() as int, options.flags.contains(IS_WHITESPACE_SHAPING_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 match self.shaper { Some(ref mut shaper) => { shaper.set_options(options); return shaper }, None => {} } let shaper = Shaper::new(self, options); self.shaper = Some(shaper); self.shaper.as_ref().unwrap() } pub fn get_table_for_tag(&self, tag: FontTableTag) -> Option { let result = self.handle.get_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()); return result; } pub fn glyph_index(&self, codepoint: char) -> Option { let codepoint = match self.variant { font_variant::T::small_caps => codepoint.to_uppercase(), 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: SmallVec8>>, } impl FontGroup { pub fn new(fonts: SmallVec8>>) -> FontGroup { FontGroup { fonts: fonts, } } pub fn create_textrun(&self, text: String, options: &ShapingOptions) -> TextRun { assert!(self.fonts.len() > 0); // TODO(Issue #177): Actually fall back through the FontGroup when a font is unsuitable. TextRun::new(&mut *self.fonts.get(0).borrow_mut(), text.clone(), options) } } 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(Point2D(Au(0), -ascent), Size2D(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, } } }