/* 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 azure::{AzFloat, AzScaledFontRef}; use azure::azure_hl::{BackendType, ColorPattern}; use azure::scaled_font::ScaledFont; use geom::{Point2D, Rect, Size2D}; use std::cast; use std::num::Zero; use std::ptr; use std::str; use std::rc::Rc; use std::cell::RefCell; use servo_util::cache::{Cache, HashCache}; use servo_util::range::Range; use style::computed_values::{text_decoration, font_weight, font_style}; use sync::Arc; use color::Color; use font_context::FontContext; use servo_util::geometry::Au; use platform::font_context::FontContextHandle; use platform::font::{FontHandle, FontTable}; use render_context::RenderContext; use text::glyph::{GlyphStore, GlyphIndex}; use text::shaping::ShaperMethods; use text::{Shaper, TextRun}; // 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_buffer(fctx: &FontContextHandle, buf: ~[u8], style: &SpecifiedFontStyle) -> Result; // an identifier usable by FontContextHandle to recreate this FontHandle. fn face_identifier(&self) -> ~str; fn family_name(&self) -> ~str; fn face_name(&self) -> ~str; fn is_italic(&self) -> bool; fn boldness(&self) -> font_weight::T; fn clone_with_style(&self, fctx: &FontContextHandle, style: &UsedFontStyle) -> Result; fn glyph_index(&self, codepoint: char) -> Option; fn glyph_h_advance(&self, GlyphIndex) -> Option; 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) -> ~str; } impl FontTableTagConversions for FontTableTag { fn tag_to_str(&self) -> ~str { unsafe { let reversed = str::raw::from_buf_len(cast::transmute(self), 4); return str::from_chars([reversed.char_at(3), reversed.char_at(2), reversed.char_at(1), reversed.char_at(0)]); } } } pub trait FontTableMethods { fn with_buffer(&self, |*u8, uint|); } #[deriving(Clone)] 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 } // TODO(Issue #179): eventually this will be split into the specified // and used font styles. specified contains uninterpreted CSS font // property values, while 'used' is attached to gfx::Font to descript // the instance's properties. // // For now, the cases are differentiated with a typedef #[deriving(Clone, Eq)] pub struct FontStyle { pub pt_size: f64, pub weight: font_weight::T, pub style: font_style::T, pub families: ~[~str], // TODO(Issue #198): font-stretch, text-decoration, font-variant, size-adjust } pub type SpecifiedFontStyle = FontStyle; pub type UsedFontStyle = FontStyle; // FontDescriptor serializes a specific font and used font style // options, such as point size. // It's used to swizzle/unswizzle gfx::Font instances when // communicating across tasks, such as the display list between layout // and render tasks. #[deriving(Clone, Eq)] pub struct FontDescriptor { pub style: UsedFontStyle, pub selector: FontSelector, } impl FontDescriptor { pub fn new(style: UsedFontStyle, selector: FontSelector) -> FontDescriptor { FontDescriptor { style: style, selector: selector, } } } // A FontSelector is a platform-specific strategy for serializing face names. #[deriving(Clone, Eq)] pub enum FontSelector { SelectorPlatformIdentifier(~str), } // This struct is the result of mapping a specified FontStyle into the // available fonts on the system. It contains an ordered list of font // instances to be used in case the prior font cannot be used for // rendering the specified language. // The ordering of font instances is mainly decided by the CSS // 'font-family' property. The last font is a system fallback font. pub struct FontGroup { pub families: ~[~str], // style of the first western font in group, which is // used for purposes of calculating text run metrics. pub style: UsedFontStyle, pub fonts: ~[Rc>] } impl FontGroup { pub fn new(families: ~[~str], style: &UsedFontStyle, fonts: ~[Rc>]) -> FontGroup { FontGroup { families: families, style: (*style).clone(), fonts: fonts, } } pub fn teardown(&mut self) { self.fonts = ~[]; } pub fn create_textrun(&self, text: ~str, decoration: text_decoration::T) -> 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[0].borrow_mut(), text.clone(), decoration) } } 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, } } } /** A font instance. Layout can use this to calculate glyph metrics and the renderer can use it to render text. */ pub struct Font { pub handle: FontHandle, pub azure_font: Option, pub shaper: Option, pub style: UsedFontStyle, pub metrics: FontMetrics, pub backend: BackendType, pub shape_cache: HashCache<~str, Arc>, pub glyph_advance_cache: HashCache, } impl<'a> Font { pub fn new_from_buffer(ctx: &FontContext, buffer: ~[u8], style: &SpecifiedFontStyle, backend: BackendType) -> Result>, ()> { let handle = FontHandleMethods::new_from_buffer(&ctx.handle, buffer, style); let handle: FontHandle = if handle.is_ok() { handle.unwrap() } else { return Err(handle.unwrap_err()); }; let metrics = handle.get_metrics(); return Ok(Rc::new(RefCell::new(Font { handle: handle, azure_font: None, shaper: None, style: (*style).clone(), metrics: metrics, backend: backend, shape_cache: HashCache::new(), glyph_advance_cache: HashCache::new(), }))); } pub fn new_from_adopted_handle(_fctx: &FontContext, handle: FontHandle, style: &SpecifiedFontStyle, backend: BackendType) -> Font { let metrics = handle.get_metrics(); Font { handle: handle, azure_font: None, shaper: None, style: (*style).clone(), metrics: metrics, backend: backend, shape_cache: HashCache::new(), glyph_advance_cache: HashCache::new(), } } pub fn new_from_existing_handle(fctx: &FontContext, handle: &FontHandle, style: &SpecifiedFontStyle, backend: BackendType) -> Result>,()> { // TODO(Issue #179): convert between specified and used font style here? let styled_handle = match handle.clone_with_style(&fctx.handle, style) { Ok(result) => result, Err(()) => return Err(()) }; return Ok(Rc::new(RefCell::new(Font::new_from_adopted_handle(fctx, styled_handle, style, backend)))); } fn make_shaper(&'a mut self) -> &'a Shaper { // fast path: already created a shaper match self.shaper { Some(ref shaper) => { let s: &'a Shaper = shaper; return s; }, None => {} } let shaper = Shaper::new(self); self.shaper = Some(shaper); self.shaper.get_ref() } 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!("{:s} font table[{:s}] with family={:s}, face={:s}", status, tag.tag_to_str(), self.handle.family_name(), self.handle.face_name()); return result; } pub fn teardown(&mut self) { self.shaper = None; self.azure_font = None; } // TODO: this should return a borrowed pointer, but I can't figure // out why borrowck doesn't like my implementation. fn get_azure_font(&mut self) -> AzScaledFontRef { // fast path: we've already created the azure font resource match self.azure_font { Some(ref azfont) => return azfont.get_ref(), None => {} } let scaled_font = self.create_azure_font(); self.azure_font = Some(scaled_font); // try again. return self.get_azure_font(); } #[cfg(target_os="macos")] fn create_azure_font(&mut self) -> ScaledFont { let cg_font = self.handle.get_CGFont(); let size = self.style.pt_size as AzFloat; ScaledFont::new(self.backend, &cg_font, size) } #[cfg(target_os="linux")] #[cfg(target_os="android")] fn create_azure_font(&self) -> ScaledFont { let freetype_font = self.handle.face; let size = self.style.pt_size as AzFloat; ScaledFont::new(self.backend, freetype_font, size) } } impl Font { pub fn draw_text_into_context(&mut self, rctx: &RenderContext, run: &~TextRun, range: &Range, baseline_origin: Point2D, color: Color) { use libc::types::common::c99::{uint16_t, uint32_t}; use azure::{struct__AzDrawOptions, struct__AzGlyph, struct__AzGlyphBuffer, struct__AzPoint}; use azure::azure::{AzDrawTargetFillGlyphs}; let target = rctx.get_draw_target(); let azfontref = self.get_azure_font(); let pattern = ColorPattern(color); let azure_pattern = pattern.azure_color_pattern; assert!(azure_pattern.is_not_null()); let options = struct__AzDrawOptions { mAlpha: 1f64 as AzFloat, fields: 0x0200 as uint16_t }; let mut origin = baseline_origin.clone(); let mut azglyphs = ~[]; azglyphs.reserve(range.length()); for (glyphs, _offset, slice_range) in run.iter_slices_for_range(range) { for (_i, glyph) in glyphs.iter_glyphs_for_char_range(&slice_range) { let glyph_advance = glyph.advance(); let glyph_offset = glyph.offset().unwrap_or(Zero::zero()); let azglyph = struct__AzGlyph { mIndex: glyph.index() as uint32_t, mPosition: struct__AzPoint { x: (origin.x + glyph_offset.x).to_nearest_px() as AzFloat, y: (origin.y + glyph_offset.y).to_nearest_px() as AzFloat } }; origin = Point2D(origin.x + glyph_advance, origin.y); azglyphs.push(azglyph) }; } let azglyph_buf_len = azglyphs.len(); if azglyph_buf_len == 0 { return; } // Otherwise the Quartz backend will assert. let glyphbuf = struct__AzGlyphBuffer { mGlyphs: azglyphs.as_ptr(), mNumGlyphs: azglyph_buf_len as uint32_t }; unsafe { // TODO(Issue #64): this call needs to move into azure_hl.rs AzDrawTargetFillGlyphs(target.azure_draw_target, azfontref, &glyphbuf, azure_pattern, &options, ptr::null()); } } pub fn measure_text(&self, run: &TextRun, range: &Range) -> RunMetrics { // TODO(Issue #199): alter advance direction for RTL // TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text let mut advance = Au(0); for (glyphs, _offset, slice_range) in run.iter_slices_for_range(range) { for (_i, glyph) in glyphs.iter_glyphs_for_char_range(&slice_range) { advance = advance + glyph.advance(); } } RunMetrics::new(advance, self.metrics.ascent, self.metrics.descent) } pub fn measure_text_for_slice(&self, glyphs: &GlyphStore, slice_range: &Range) -> RunMetrics { let mut advance = Au(0); for (_i, glyph) in glyphs.iter_glyphs_for_char_range(slice_range) { advance = advance + glyph.advance(); } RunMetrics::new(advance, self.metrics.ascent, self.metrics.descent) } pub fn shape_text(&mut self, text: ~str, is_whitespace: bool) -> Arc { //FIXME (ksh8281) self.make_shaper(); let shaper = &self.shaper; self.shape_cache.find_or_create(&text, |txt| { let mut glyphs = GlyphStore::new(text.char_len(), is_whitespace); shaper.get_ref().shape_text(*txt, &mut glyphs); Arc::new(glyphs) }) } pub fn get_descriptor(&self) -> FontDescriptor { FontDescriptor::new(self.style.clone(), SelectorPlatformIdentifier(self.handle.face_identifier())) } pub fn glyph_index(&self, codepoint: char) -> Option { self.handle.glyph_index(codepoint) } pub fn glyph_h_advance(&mut self, glyph: GlyphIndex) -> FractionalPixel { let handle = &self.handle; self.glyph_advance_cache.find_or_create(&glyph, |glyph| { match handle.glyph_h_advance(*glyph) { Some(adv) => adv, None => /* FIXME: Need fallback strategy */ 10f64 as FractionalPixel } }) } }