diff options
author | Jack Moffitt <jack@metajack.im> | 2014-08-28 09:34:23 -0600 |
---|---|---|
committer | Jack Moffitt <jack@metajack.im> | 2014-09-08 20:21:42 -0600 |
commit | c6ab60dbfc6da7b4f800c9e40893c8b58413960c (patch) | |
tree | d1d74076cf7fa20e4f77ec7cb82cae98b67362cb /components/gfx | |
parent | db2f642c32fc5bed445bb6f2e45b0f6f0b4342cf (diff) | |
download | servo-c6ab60dbfc6da7b4f800c9e40893c8b58413960c.tar.gz servo-c6ab60dbfc6da7b4f800c9e40893c8b58413960c.zip |
Cargoify servo
Diffstat (limited to 'components/gfx')
27 files changed, 5533 insertions, 0 deletions
diff --git a/components/gfx/Cargo.toml b/components/gfx/Cargo.toml new file mode 100644 index 00000000000..fdac5770f70 --- /dev/null +++ b/components/gfx/Cargo.toml @@ -0,0 +1,62 @@ +[package] + +name = "gfx" +version = "0.0.1" +authors = ["The Servo Project Developers"] + +[lib] +name = "gfx" +path = "lib.rs" + +[dependencies.macros] +path = "../macros" + +[dependencies.net] +path = "../net" + +[dependencies.util] +path = "../util" + +[dependencies.msg] +path = "../msg" + +[dependencies.style] +path = "../style" + +[dependencies.azure] +git = "https://github.com/servo/rust-azure" + +[dependencies.geom] +git = "https://github.com/servo/rust-geom" + +[dependencies.layers] +git = "https://github.com/servo/rust-layers" + +[dependencies.stb_image] +git = "https://github.com/servo/rust-stb-image" + +[dependencies.png] +git = "https://github.com/servo/rust-png" + +[dependencies.url] +git = "https://github.com/servo/rust-url" + +[dependencies.harfbuzz] +git = "https://github.com/servo/rust-harfbuzz" + +[dependencies.fontconfig] +git = "https://github.com/servo/rust-fontconfig" + +[dependencies.freetype] +git = "https://github.com/servo/rust-freetype" + +[dependencies.core_foundation] +git = "http://github.com/servo/rust-core-foundation" + +[dependencies.core_graphics] +git = "http://github.com/servo/rust-core-graphics" + +[dependencies.core_text] +git = "http://github.com/servo/rust-core-text" + + diff --git a/components/gfx/buffer_map.rs b/components/gfx/buffer_map.rs new file mode 100644 index 00000000000..0551385f717 --- /dev/null +++ b/components/gfx/buffer_map.rs @@ -0,0 +1,156 @@ +/* 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 std::collections::hashmap::HashMap; +use geom::size::Size2D; +use layers::platform::surface::NativePaintingGraphicsContext; +use layers::layers::LayerBuffer; +use std::hash::Hash; +use std::hash::sip::SipState; +use std::mem; + +/// This is a struct used to store buffers when they are not in use. +/// The render task can quickly query for a particular size of buffer when it +/// needs it. +pub struct BufferMap { + /// A HashMap that stores the Buffers. + map: HashMap<BufferKey, BufferValue>, + /// The current amount of memory stored by the BufferMap's buffers. + mem: uint, + /// The maximum allowed memory. Unused buffers will be deleted + /// when this threshold is exceeded. + max_mem: uint, + /// A monotonically increasing counter to track how recently tile sizes were used. + counter: uint, +} + +/// A key with which to store buffers. It is based on the size of the buffer. +#[deriving(Eq)] +struct BufferKey([uint, ..2]); + +impl Hash for BufferKey { + fn hash(&self, state: &mut SipState) { + let BufferKey(ref bytes) = *self; + bytes.as_slice().hash(state); + } +} + +impl PartialEq for BufferKey { + fn eq(&self, other: &BufferKey) -> bool { + let BufferKey(s) = *self; + let BufferKey(o) = *other; + s[0] == o[0] && s[1] == o[1] + } +} + +/// Create a key from a given size +impl BufferKey { + fn get(input: Size2D<uint>) -> BufferKey { + BufferKey([input.width, input.height]) + } +} + +/// A helper struct to keep track of buffers in the HashMap +struct BufferValue { + /// An array of buffers, all the same size + buffers: Vec<Box<LayerBuffer>>, + /// The counter when this size was last requested + last_action: uint, +} + +impl BufferMap { + // Creates a new BufferMap with a given buffer limit. + pub fn new(max_mem: uint) -> BufferMap { + BufferMap { + map: HashMap::new(), + mem: 0u, + max_mem: max_mem, + counter: 0u, + } + } + + /// Insert a new buffer into the map. + pub fn insert(&mut self, graphics_context: &NativePaintingGraphicsContext, new_buffer: Box<LayerBuffer>) { + let new_key = BufferKey::get(new_buffer.get_size_2d()); + + // If all our buffers are the same size and we're already at our + // memory limit, no need to store this new buffer; just let it drop. + if self.mem + new_buffer.get_mem() > self.max_mem && self.map.len() == 1 && + self.map.contains_key(&new_key) { + new_buffer.destroy(graphics_context); + return; + } + + self.mem += new_buffer.get_mem(); + // use lazy insertion function to prevent unnecessary allocation + let counter = &self.counter; + self.map.find_or_insert_with(new_key, |_| BufferValue { + buffers: vec!(), + last_action: *counter + }).buffers.push(new_buffer); + + let mut opt_key: Option<BufferKey> = None; + while self.mem > self.max_mem { + let old_key = match opt_key { + Some(key) => key, + None => { + match self.map.iter().min_by(|&(_, x)| x.last_action) { + Some((k, _)) => *k, + None => fail!("BufferMap: tried to delete with no elements in map"), + } + } + }; + if { + let list = &mut self.map.get_mut(&old_key).buffers; + let condemned_buffer = list.pop().take_unwrap(); + self.mem -= condemned_buffer.get_mem(); + condemned_buffer.destroy(graphics_context); + list.is_empty() + } + { // then + self.map.pop(&old_key); // Don't store empty vectors! + opt_key = None; + } else { + opt_key = Some(old_key); + } + } + } + + // Try to find a buffer for the given size. + pub fn find(&mut self, size: Size2D<uint>) -> Option<Box<LayerBuffer>> { + let mut flag = false; // True if key needs to be popped after retrieval. + let key = BufferKey::get(size); + let ret = match self.map.find_mut(&key) { + Some(ref mut buffer_val) => { + buffer_val.last_action = self.counter; + self.counter += 1; + + let buffer = buffer_val.buffers.pop().take_unwrap(); + self.mem -= buffer.get_mem(); + if buffer_val.buffers.is_empty() { + flag = true; + } + Some(buffer) + } + None => None, + }; + + if flag { + self.map.pop(&key); // Don't store empty vectors! + } + + ret + } + + /// Destroys all buffers. + pub fn clear(&mut self, graphics_context: &NativePaintingGraphicsContext) { + let map = mem::replace(&mut self.map, HashMap::new()); + for (_, value) in map.move_iter() { + for tile in value.buffers.move_iter() { + tile.destroy(graphics_context) + } + } + self.mem = 0 + } +} diff --git a/components/gfx/color.rs b/components/gfx/color.rs new file mode 100644 index 00000000000..ffd5b5ed2b2 --- /dev/null +++ b/components/gfx/color.rs @@ -0,0 +1,21 @@ +/* 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; +use AzColor = azure::azure_hl::Color; + +pub type Color = AzColor; + +pub fn rgb(r: u8, g: u8, b: u8) -> AzColor { + AzColor { + r: (r as AzFloat) / (255.0 as AzFloat), + g: (g as AzFloat) / (255.0 as AzFloat), + b: (b as AzFloat) / (255.0 as AzFloat), + a: 1.0 as AzFloat + } +} + +pub fn rgba(r: AzFloat, g: AzFloat, b: AzFloat, a: AzFloat) -> AzColor { + AzColor { r: r, g: g, b: b, a: a } +} diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs new file mode 100644 index 00000000000..e0796c61fb2 --- /dev/null +++ b/components/gfx/display_list/mod.rs @@ -0,0 +1,773 @@ +/* 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/. */ + +//! Servo heavily uses display lists, which are retained-mode lists of rendering commands to +//! perform. Using a list instead of rendering elements in immediate mode allows transforms, hit +//! testing, and invalidation to be performed using the same primitives as painting. It also allows +//! Servo to aggressively cull invisible and out-of-bounds rendering elements, to reduce overdraw. +//! Finally, display lists allow tiles to be farmed out onto multiple CPUs and rendered in +//! parallel (although this benefit does not apply to GPU-based rendering). +//! +//! Display items describe relatively high-level drawing operations (for example, entire borders +//! and shadows instead of lines and blur operations), to reduce the amount of allocation required. +//! They are therefore not exactly analogous to constructs like Skia pictures, which consist of +//! low-level drawing primitives. + +use color::Color; +use render_context::RenderContext; +use text::glyph::CharIndex; +use text::TextRun; + +use collections::dlist::DList; +use collections::dlist; +use geom::{Point2D, Rect, SideOffsets2D, Size2D, Matrix2D}; +use libc::uintptr_t; +use servo_net::image::base::Image; +use servo_util::geometry::Au; +use servo_util::range::Range; +use std::fmt; +use std::mem; +use std::slice::Items; +use style::computed_values::border_style; +use sync::Arc; +use std::num::Zero; +use std::ptr; + +use azure::AzFloat; +use azure::scaled_font::ScaledFont; +use azure::azure_hl::ColorPattern; + +pub mod optimizer; + +/// An opaque handle to a node. The only safe operation that can be performed on this node is to +/// compare it to another opaque handle or to another node. +/// +/// Because the script task's GC does not trace layout, node data cannot be safely stored in layout +/// data structures. Also, layout code tends to be faster when the DOM is not being accessed, for +/// locality reasons. Using `OpaqueNode` enforces this invariant. +#[deriving(Clone, PartialEq)] +pub struct OpaqueNode(pub uintptr_t); + +impl OpaqueNode { + /// Returns the address of this node, for debugging purposes. + pub fn id(&self) -> uintptr_t { + let OpaqueNode(pointer) = *self; + pointer + } +} + +trait ScaledFontExtensionMethods { + fn draw_text_into_context(&self, + rctx: &RenderContext, + run: &Box<TextRun>, + range: &Range<CharIndex>, + baseline_origin: Point2D<Au>, + color: Color, + antialias: bool); +} + +impl ScaledFontExtensionMethods for ScaledFont { + fn draw_text_into_context(&self, + rctx: &RenderContext, + run: &Box<TextRun>, + range: &Range<CharIndex>, + baseline_origin: Point2D<Au>, + color: Color, + antialias: bool) { + use libc::types::common::c99::uint32_t; + use azure::{struct__AzDrawOptions, + struct__AzGlyph, + struct__AzGlyphBuffer, + struct__AzPoint}; + use azure::azure::{AzDrawTargetFillGlyphs}; + + let target = rctx.get_draw_target(); + let pattern = ColorPattern::new(color); + let azure_pattern = pattern.azure_color_pattern; + assert!(azure_pattern.is_not_null()); + + let fields = if antialias { + 0x0200 + } else { + 0 + }; + + let mut options = struct__AzDrawOptions { + mAlpha: 1f64 as AzFloat, + fields: fields, + }; + + let mut origin = baseline_origin.clone(); + let mut azglyphs = vec!(); + azglyphs.reserve(range.length().to_uint()); + + 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.id() 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 mut glyphbuf = struct__AzGlyphBuffer { + mGlyphs: azglyphs.as_mut_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, + self.get_ref(), + &mut glyphbuf, + azure_pattern, + &mut options, + ptr::mut_null()); + } + } +} + +/// "Steps" as defined by CSS 2.1 § E.2. +#[deriving(Clone, PartialEq)] +pub enum StackingLevel { + /// The border and backgrounds for the root of this stacking context: steps 1 and 2. + BackgroundAndBordersStackingLevel, + /// Borders and backgrounds for block-level descendants: step 4. + BlockBackgroundsAndBordersStackingLevel, + /// Floats: step 5. These are treated as pseudo-stacking contexts. + FloatStackingLevel, + /// All other content. + ContentStackingLevel, + /// Positioned descendant stacking contexts, along with their `z-index` levels. + /// + /// TODO(pcwalton): `z-index` should be the actual CSS property value in order to handle + /// `auto`, not just an integer. + PositionedDescendantStackingLevel(i32) +} + +impl StackingLevel { + pub fn from_background_and_border_level(level: BackgroundAndBorderLevel) -> StackingLevel { + match level { + RootOfStackingContextLevel => BackgroundAndBordersStackingLevel, + BlockLevel => BlockBackgroundsAndBordersStackingLevel, + ContentLevel => ContentStackingLevel, + } + } +} + +struct StackingContext { + /// The border and backgrounds for the root of this stacking context: steps 1 and 2. + pub background_and_borders: DisplayList, + /// Borders and backgrounds for block-level descendants: step 4. + pub block_backgrounds_and_borders: DisplayList, + /// Floats: step 5. These are treated as pseudo-stacking contexts. + pub floats: DisplayList, + /// All other content. + pub content: DisplayList, + /// Positioned descendant stacking contexts, along with their `z-index` levels. + /// + /// TODO(pcwalton): `z-index` should be the actual CSS property value in order to handle + /// `auto`, not just an integer. + pub positioned_descendants: Vec<(i32, DisplayList)>, +} + +impl StackingContext { + /// Creates a stacking context from a display list. + fn new(list: DisplayList) -> StackingContext { + let DisplayList { + list: list + } = list; + + let mut stacking_context = StackingContext { + background_and_borders: DisplayList::new(), + block_backgrounds_and_borders: DisplayList::new(), + floats: DisplayList::new(), + content: DisplayList::new(), + positioned_descendants: Vec::new(), + }; + + for item in list.move_iter() { + match item { + ClipDisplayItemClass(box ClipDisplayItem { + base: base, + children: sublist + }) => { + let sub_stacking_context = StackingContext::new(sublist); + stacking_context.merge_with_clip(sub_stacking_context, &base.bounds, base.node) + } + item => { + match item.base().level { + BackgroundAndBordersStackingLevel => { + stacking_context.background_and_borders.push(item) + } + BlockBackgroundsAndBordersStackingLevel => { + stacking_context.block_backgrounds_and_borders.push(item) + } + FloatStackingLevel => stacking_context.floats.push(item), + ContentStackingLevel => stacking_context.content.push(item), + PositionedDescendantStackingLevel(z_index) => { + match stacking_context.positioned_descendants + .mut_iter() + .find(|& &(z, _)| z_index == z) { + Some(&(_, ref mut my_list)) => { + my_list.push(item); + continue + } + None => {} + } + + let mut new_list = DisplayList::new(); + new_list.list.push(item); + stacking_context.positioned_descendants.push((z_index, new_list)) + } + } + } + } + } + + stacking_context + } + + /// Merges another stacking context into this one, with the given clipping rectangle and DOM + /// node that supplies it. + fn merge_with_clip(&mut self, + other: StackingContext, + clip_rect: &Rect<Au>, + clipping_dom_node: OpaqueNode) { + let StackingContext { + background_and_borders, + block_backgrounds_and_borders, + floats, + content, + positioned_descendants: positioned_descendants + } = other; + + let push = |destination: &mut DisplayList, source: DisplayList, level| { + if !source.is_empty() { + let base = BaseDisplayItem::new(*clip_rect, clipping_dom_node, level); + destination.push(ClipDisplayItemClass(box ClipDisplayItem::new(base, source))) + } + }; + + push(&mut self.background_and_borders, + background_and_borders, + BackgroundAndBordersStackingLevel); + push(&mut self.block_backgrounds_and_borders, + block_backgrounds_and_borders, + BlockBackgroundsAndBordersStackingLevel); + push(&mut self.floats, floats, FloatStackingLevel); + push(&mut self.content, content, ContentStackingLevel); + + for (z_index, list) in positioned_descendants.move_iter() { + match self.positioned_descendants + .mut_iter() + .find(|& &(existing_z_index, _)| z_index == existing_z_index) { + Some(&(_, ref mut existing_list)) => { + push(existing_list, list, PositionedDescendantStackingLevel(z_index)); + continue + } + None => {} + } + + let mut new_list = DisplayList::new(); + push(&mut new_list, list, PositionedDescendantStackingLevel(z_index)); + self.positioned_descendants.push((z_index, new_list)); + } + } +} + +/// Which level to place backgrounds and borders in. +pub enum BackgroundAndBorderLevel { + RootOfStackingContextLevel, + BlockLevel, + ContentLevel, +} + +/// A list of rendering operations to be performed. +#[deriving(Clone)] +pub struct DisplayList { + pub list: DList<DisplayItem>, +} + +pub enum DisplayListIterator<'a> { + EmptyDisplayListIterator, + ParentDisplayListIterator(Items<'a,DisplayList>), +} + +impl<'a> Iterator<&'a DisplayList> for DisplayListIterator<'a> { + #[inline] + fn next(&mut self) -> Option<&'a DisplayList> { + match *self { + EmptyDisplayListIterator => None, + ParentDisplayListIterator(ref mut subiterator) => subiterator.next(), + } + } +} + +impl DisplayList { + /// Creates a new display list. + pub fn new() -> DisplayList { + DisplayList { + list: DList::new(), + } + } + + /// Appends the given item to the display list. + pub fn push(&mut self, item: DisplayItem) { + self.list.push(item) + } + + /// Appends the given display list to this display list, consuming the other display list in + /// the process. + pub fn push_all_move(&mut self, other: DisplayList) { + self.list.append(other.list) + } + + pub fn debug(&self) { + if log_enabled!(::log::DEBUG) { + for item in self.list.iter() { + item.debug_with_level(0); + } + } + } + + /// Draws the display list into the given render context. The display list must be flattened + /// first for correct painting. + pub fn draw_into_context(&self, render_context: &mut RenderContext, + current_transform: &Matrix2D<AzFloat>) { + debug!("Beginning display list."); + for item in self.list.iter() { + item.draw_into_context(render_context, current_transform) + } + debug!("Ending display list."); + } + + /// Returns a preorder iterator over the given display list. + pub fn iter<'a>(&'a self) -> DisplayItemIterator<'a> { + ParentDisplayItemIterator(self.list.iter()) + } + + /// Returns true if this list is empty and false otherwise. + fn is_empty(&self) -> bool { + self.list.len() == 0 + } + + /// Flattens a display list into a display list with a single stacking level according to the + /// steps in CSS 2.1 § E.2. + /// + /// This must be called before `draw_into_context()` is for correct results. + pub fn flatten(self, resulting_level: StackingLevel) -> DisplayList { + // TODO(pcwalton): Sort positioned children according to z-index. + + let mut result = DisplayList::new(); + let StackingContext { + background_and_borders, + block_backgrounds_and_borders, + floats, + content, + positioned_descendants: mut positioned_descendants + } = StackingContext::new(self); + + // Steps 1 and 2: Borders and background for the root. + result.push_all_move(background_and_borders); + + // TODO(pcwalton): Sort positioned children according to z-index. + + // Step 3: Positioned descendants with negative z-indices. + for &(ref mut z_index, ref mut list) in positioned_descendants.mut_iter() { + if *z_index < 0 { + result.push_all_move(mem::replace(list, DisplayList::new())) + } + } + + // Step 4: Block backgrounds and borders. + result.push_all_move(block_backgrounds_and_borders); + + // Step 5: Floats. + result.push_all_move(floats); + + // TODO(pcwalton): Step 6: Inlines that generate stacking contexts. + + // Step 7: Content. + result.push_all_move(content); + + // Steps 8 and 9: Positioned descendants with nonnegative z-indices. + for &(ref mut z_index, ref mut list) in positioned_descendants.mut_iter() { + if *z_index >= 0 { + result.push_all_move(mem::replace(list, DisplayList::new())) + } + } + + // TODO(pcwalton): Step 10: Outlines. + + result.set_stacking_level(resulting_level); + result + } + + /// Sets the stacking level for this display list and all its subitems. + fn set_stacking_level(&mut self, new_level: StackingLevel) { + for item in self.list.mut_iter() { + item.mut_base().level = new_level; + match item.mut_sublist() { + None => {} + Some(sublist) => sublist.set_stacking_level(new_level), + } + } + } +} + +/// One drawing command in the list. +#[deriving(Clone)] +pub enum DisplayItem { + SolidColorDisplayItemClass(Box<SolidColorDisplayItem>), + TextDisplayItemClass(Box<TextDisplayItem>), + ImageDisplayItemClass(Box<ImageDisplayItem>), + BorderDisplayItemClass(Box<BorderDisplayItem>), + LineDisplayItemClass(Box<LineDisplayItem>), + ClipDisplayItemClass(Box<ClipDisplayItem>), + + /// A pseudo-display item that exists only so that queries like `ContentBoxQuery` and + /// `ContentBoxesQuery` can be answered. + /// + /// FIXME(pcwalton): This is really bogus. Those queries should not consult the display list + /// but should instead consult the flow/box tree. + PseudoDisplayItemClass(Box<BaseDisplayItem>), +} + +/// Information common to all display items. +#[deriving(Clone)] +pub struct BaseDisplayItem { + /// The boundaries of the display item. + /// + /// TODO: Which coordinate system should this use? + pub bounds: Rect<Au>, + + /// The originating DOM node. + pub node: OpaqueNode, + + /// The stacking level in which this display item lives. + pub level: StackingLevel, +} + +impl BaseDisplayItem { + pub fn new(bounds: Rect<Au>, node: OpaqueNode, level: StackingLevel) -> BaseDisplayItem { + BaseDisplayItem { + bounds: bounds, + node: node, + level: level, + } + } +} + +/// Renders a solid color. +#[deriving(Clone)] +pub struct SolidColorDisplayItem { + pub base: BaseDisplayItem, + pub color: Color, +} + +/// Renders text. +#[deriving(Clone)] +pub struct TextDisplayItem { + /// Fields common to all display items. + pub base: BaseDisplayItem, + + /// The text run. + pub text_run: Arc<Box<TextRun>>, + + /// The range of text within the text run. + pub range: Range<CharIndex>, + + /// The color of the text. + pub text_color: Color, + + pub baseline_origin: Point2D<Au>, + pub orientation: TextOrientation, +} + +#[deriving(Clone, Eq, PartialEq)] +pub enum TextOrientation { + Upright, + SidewaysLeft, + SidewaysRight, +} + +/// Renders an image. +#[deriving(Clone)] +pub struct ImageDisplayItem { + pub base: BaseDisplayItem, + pub image: Arc<Box<Image>>, + + /// The dimensions to which the image display item should be stretched. If this is smaller than + /// the bounds of this display item, then the image will be repeated in the appropriate + /// direction to tile the entire bounds. + pub stretch_size: Size2D<Au>, +} + +/// Renders a border. +#[deriving(Clone)] +pub struct BorderDisplayItem { + pub base: BaseDisplayItem, + + /// The border widths + pub border: SideOffsets2D<Au>, + + /// The border colors. + pub color: SideOffsets2D<Color>, + + /// The border styles. + pub style: SideOffsets2D<border_style::T> +} + +/// Renders a line segment. +#[deriving(Clone)] +pub struct LineDisplayItem { + pub base: BaseDisplayItem, + + /// The line segment color. + pub color: Color, + + /// The line segment style. + pub style: border_style::T +} + +/// Clips a list of child display items to this display item's boundaries. +#[deriving(Clone)] +pub struct ClipDisplayItem { + /// The base information. + pub base: BaseDisplayItem, + + /// The child nodes. + pub children: DisplayList, +} + +impl ClipDisplayItem { + pub fn new(base: BaseDisplayItem, children: DisplayList) -> ClipDisplayItem { + ClipDisplayItem { + base: base, + children: children, + } + } +} + +pub enum DisplayItemIterator<'a> { + EmptyDisplayItemIterator, + ParentDisplayItemIterator(dlist::Items<'a,DisplayItem>), +} + +impl<'a> Iterator<&'a DisplayItem> for DisplayItemIterator<'a> { + #[inline] + fn next(&mut self) -> Option<&'a DisplayItem> { + match *self { + EmptyDisplayItemIterator => None, + ParentDisplayItemIterator(ref mut subiterator) => subiterator.next(), + } + } +} + +impl DisplayItem { + /// Renders this display item into the given render context. + fn draw_into_context(&self, render_context: &mut RenderContext, + current_transform: &Matrix2D<AzFloat>) { + // This should have been flattened to the content stacking level first. + assert!(self.base().level == ContentStackingLevel); + + match *self { + SolidColorDisplayItemClass(ref solid_color) => { + render_context.draw_solid_color(&solid_color.base.bounds, solid_color.color) + } + + ClipDisplayItemClass(ref clip) => { + render_context.draw_push_clip(&clip.base.bounds); + for item in clip.children.iter() { + (*item).draw_into_context(render_context, current_transform); + } + render_context.draw_pop_clip(); + } + + TextDisplayItemClass(ref text) => { + debug!("Drawing text at {}.", text.base.bounds); + + // Optimization: Don’t set a transform matrix for upright text, + // and pass a strart point to `draw_text_into_context`. + // For sideways text, it’s easier to do the rotation such that its center + // (the baseline’s start point) is at (0, 0) coordinates. + let baseline_origin = match text.orientation { + Upright => text.baseline_origin, + SidewaysLeft => { + let x = text.baseline_origin.x.to_nearest_px() as AzFloat; + let y = text.baseline_origin.y.to_nearest_px() as AzFloat; + render_context.draw_target.set_transform(¤t_transform.mul( + &Matrix2D::new( + 0., -1., + 1., 0., + x, y + ) + )); + Zero::zero() + }, + SidewaysRight => { + let x = text.baseline_origin.x.to_nearest_px() as AzFloat; + let y = text.baseline_origin.y.to_nearest_px() as AzFloat; + render_context.draw_target.set_transform(¤t_transform.mul( + &Matrix2D::new( + 0., 1., + -1., 0., + x, y + ) + )); + Zero::zero() + } + }; + + render_context.font_ctx.get_render_font_from_template( + &text.text_run.font_template, + text.text_run.pt_size, + render_context.opts.render_backend + ).borrow().draw_text_into_context( + render_context, + &*text.text_run, + &text.range, + baseline_origin, + text.text_color, + render_context.opts.enable_text_antialiasing + ); + + // Undo the transform, only when we did one. + if text.orientation != Upright { + render_context.draw_target.set_transform(current_transform) + } + } + + ImageDisplayItemClass(ref image_item) => { + debug!("Drawing image at {:?}.", image_item.base.bounds); + + let mut y_offset = Au(0); + while y_offset < image_item.base.bounds.size.height { + let mut x_offset = Au(0); + while x_offset < image_item.base.bounds.size.width { + let mut bounds = image_item.base.bounds; + bounds.origin.x = bounds.origin.x + x_offset; + bounds.origin.y = bounds.origin.y + y_offset; + bounds.size = image_item.stretch_size; + + render_context.draw_image(bounds, image_item.image.clone()); + + x_offset = x_offset + image_item.stretch_size.width; + } + + y_offset = y_offset + image_item.stretch_size.height; + } + } + + BorderDisplayItemClass(ref border) => { + render_context.draw_border(&border.base.bounds, + border.border, + border.color, + border.style) + } + + LineDisplayItemClass(ref line) => { + render_context.draw_line(&line.base.bounds, + line.color, + line.style) + } + + PseudoDisplayItemClass(_) => {} + } + } + + pub fn base<'a>(&'a self) -> &'a BaseDisplayItem { + match *self { + SolidColorDisplayItemClass(ref solid_color) => &solid_color.base, + TextDisplayItemClass(ref text) => &text.base, + ImageDisplayItemClass(ref image_item) => &image_item.base, + BorderDisplayItemClass(ref border) => &border.base, + LineDisplayItemClass(ref line) => &line.base, + ClipDisplayItemClass(ref clip) => &clip.base, + PseudoDisplayItemClass(ref base) => &**base, + } + } + + pub fn mut_base<'a>(&'a mut self) -> &'a mut BaseDisplayItem { + match *self { + SolidColorDisplayItemClass(ref mut solid_color) => &mut solid_color.base, + TextDisplayItemClass(ref mut text) => &mut text.base, + ImageDisplayItemClass(ref mut image_item) => &mut image_item.base, + BorderDisplayItemClass(ref mut border) => &mut border.base, + LineDisplayItemClass(ref mut line) => &mut line.base, + ClipDisplayItemClass(ref mut clip) => &mut clip.base, + PseudoDisplayItemClass(ref mut base) => &mut **base, + } + } + + pub fn bounds(&self) -> Rect<Au> { + self.base().bounds + } + + pub fn children<'a>(&'a self) -> DisplayItemIterator<'a> { + match *self { + ClipDisplayItemClass(ref clip) => ParentDisplayItemIterator(clip.children.list.iter()), + SolidColorDisplayItemClass(..) | + TextDisplayItemClass(..) | + ImageDisplayItemClass(..) | + BorderDisplayItemClass(..) | + LineDisplayItemClass(..) | + PseudoDisplayItemClass(..) => EmptyDisplayItemIterator, + } + } + + /// Returns a mutable reference to the sublist contained within this display list item, if any. + fn mut_sublist<'a>(&'a mut self) -> Option<&'a mut DisplayList> { + match *self { + ClipDisplayItemClass(ref mut clip) => Some(&mut clip.children), + SolidColorDisplayItemClass(..) | + TextDisplayItemClass(..) | + ImageDisplayItemClass(..) | + BorderDisplayItemClass(..) | + LineDisplayItemClass(..) | + PseudoDisplayItemClass(..) => None, + } + } + + pub fn debug_with_level(&self, level: uint) { + let mut indent = String::new(); + for _ in range(0, level) { + indent.push_str("| ") + } + debug!("{}+ {}", indent, self); + for child in self.children() { + child.debug_with_level(level + 1); + } + } +} + +impl fmt::Show for DisplayItem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} @ {} ({:x})", + match *self { + SolidColorDisplayItemClass(_) => "SolidColor", + TextDisplayItemClass(_) => "Text", + ImageDisplayItemClass(_) => "Image", + BorderDisplayItemClass(_) => "Border", + LineDisplayItemClass(_) => "Line", + ClipDisplayItemClass(_) => "Clip", + PseudoDisplayItemClass(_) => "Pseudo", + }, + self.base().bounds, + self.base().node.id(), + ) + } +} diff --git a/components/gfx/display_list/optimizer.rs b/components/gfx/display_list/optimizer.rs new file mode 100644 index 00000000000..5e32238704c --- /dev/null +++ b/components/gfx/display_list/optimizer.rs @@ -0,0 +1,73 @@ +/* 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 display_list::{BorderDisplayItemClass, ClipDisplayItem, ClipDisplayItemClass, DisplayItem}; +use display_list::{DisplayList, ImageDisplayItemClass, LineDisplayItemClass}; +use display_list::{PseudoDisplayItemClass, SolidColorDisplayItemClass, TextDisplayItemClass}; + +use collections::dlist::DList; +use geom::rect::Rect; +use servo_util::geometry::Au; +use sync::Arc; + +pub struct DisplayListOptimizer { + display_list: Arc<DisplayList>, + /// The visible rect in page coordinates. + visible_rect: Rect<Au>, +} + +impl DisplayListOptimizer { + /// `visible_rect` specifies the visible rect in page coordinates. + pub fn new(display_list: Arc<DisplayList>, visible_rect: Rect<Au>) -> DisplayListOptimizer { + DisplayListOptimizer { + display_list: display_list, + visible_rect: visible_rect, + } + } + + pub fn optimize(self) -> DisplayList { + self.process_display_list(&*self.display_list) + } + + fn process_display_list(&self, display_list: &DisplayList) -> DisplayList { + let mut result = DList::new(); + for item in display_list.iter() { + match self.process_display_item(item) { + None => {} + Some(display_item) => result.push(display_item), + } + } + DisplayList { + list: result, + } + } + + fn process_display_item(&self, display_item: &DisplayItem) -> Option<DisplayItem> { + // Eliminate display items outside the visible region. + if !self.visible_rect.intersects(&display_item.base().bounds) { + return None + } + + // Recur. + match *display_item { + ClipDisplayItemClass(ref clip) => { + let new_children = self.process_display_list(&clip.children); + if new_children.is_empty() { + return None + } + Some(ClipDisplayItemClass(box ClipDisplayItem { + base: clip.base.clone(), + children: new_children, + })) + } + + BorderDisplayItemClass(_) | ImageDisplayItemClass(_) | LineDisplayItemClass(_) | + PseudoDisplayItemClass(_) | SolidColorDisplayItemClass(_) | + TextDisplayItemClass(_) => { + Some((*display_item).clone()) + } + } + } +} + diff --git a/components/gfx/font.rs b/components/gfx/font.rs new file mode 100644 index 00000000000..74930da0b4a --- /dev/null +++ b/components/gfx/font.rs @@ -0,0 +1,213 @@ +/* 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::mem; +use std::string; +use std::rc::Rc; +use std::cell::RefCell; +use servo_util::cache::{Cache, HashCache}; +use style::computed_values::{font_weight, font_style}; +use sync::Arc; + +use servo_util::geometry::Au; +use platform::font_context::FontContextHandle; +use platform::font::{FontHandle, FontTable}; +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<FontTemplateData>, pt_size: Option<f64>) + -> Result<Self,()>; + fn get_template(&self) -> Arc<FontTemplateData>; + fn family_name(&self) -> String; + fn face_name(&self) -> String; + fn is_italic(&self) -> bool; + fn boldness(&self) -> font_weight::T; + + fn glyph_index(&self, codepoint: char) -> Option<GlyphId>; + fn glyph_h_advance(&self, GlyphId) -> Option<FractionalPixel>; + fn glyph_h_kerning(&self, GlyphId, GlyphId) -> FractionalPixel; + fn get_metrics(&self) -> FontMetrics; + fn get_table_for_tag(&self, FontTableTag) -> Option<FontTable>; +} + +// 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 reversed = string::raw::from_buf_len(mem::transmute(self), 4); + return String::from_chars([reversed.as_slice().char_at(3), + reversed.as_slice().char_at(2), + reversed.as_slice().char_at(1), + reversed.as_slice().char_at(0)]); + } + } +} + +pub trait FontTableMethods { + fn with_buffer(&self, |*const 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, + pub line_gap: 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, PartialEq)] +pub struct FontStyle { + pub pt_size: f64, + pub weight: font_weight::T, + pub style: font_style::T, + pub families: Vec<String>, + // TODO(Issue #198): font-stretch, text-decoration, font-variant, size-adjust +} + +pub type SpecifiedFontStyle = FontStyle; +pub type UsedFontStyle = FontStyle; + +pub struct Font { + pub handle: FontHandle, + pub metrics: FontMetrics, + pub descriptor: FontTemplateDescriptor, + pub pt_size: f64, + pub shaper: Option<Shaper>, + pub shape_cache: HashCache<String, Arc<GlyphStore>>, + pub glyph_advance_cache: HashCache<u32, FractionalPixel>, +} + +impl Font { + pub fn shape_text(&mut self, text: String, is_whitespace: bool) -> Arc<GlyphStore> { + self.make_shaper(); + let shaper = &self.shaper; + self.shape_cache.find_or_create(&text, |txt| { + let mut glyphs = GlyphStore::new(text.as_slice().char_len() as int, is_whitespace); + shaper.get_ref().shape_text(txt.as_slice(), &mut glyphs); + Arc::new(glyphs) + }) + } + + fn make_shaper<'a>(&'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<FontTable> { + 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={}, face={}", + status, tag.tag_to_str(), + self.handle.family_name(), self.handle.face_name()); + + return result; + } + + pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> { + 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: Vec<Rc<RefCell<Font>>>, +} + +impl FontGroup { + pub fn new(fonts: Vec<Rc<RefCell<Font>>>) -> FontGroup { + FontGroup { + fonts: fonts + } + } + + pub fn create_textrun(&self, text: String) -> 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()) + } +} + +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<Au> +} + +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, + } + } +} diff --git a/components/gfx/font_cache_task.rs b/components/gfx/font_cache_task.rs new file mode 100644 index 00000000000..1b1ff6227cb --- /dev/null +++ b/components/gfx/font_cache_task.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 http://mozilla.org/MPL/2.0/. */ + +use platform::font_list::get_available_families; +use platform::font_list::get_variations_for_family; +use platform::font_list::get_last_resort_font_families; +use platform::font_context::FontContextHandle; + +use std::collections::HashMap; +use sync::Arc; +use font_template::{FontTemplate, FontTemplateDescriptor}; +use platform::font_template::FontTemplateData; +use servo_net::resource_task::{ResourceTask, load_whole_resource}; +use url::Url; + +/// A list of font templates that make up a given font family. +struct FontFamily { + templates: Vec<FontTemplate>, +} + +impl FontFamily { + fn new() -> FontFamily { + FontFamily { + templates: vec!(), + } + } + + /// Find a font in this family that matches a given desriptor. + fn find_font_for_style<'a>(&'a mut self, desc: &FontTemplateDescriptor, fctx: &FontContextHandle) + -> Option<Arc<FontTemplateData>> { + // TODO(Issue #189): optimize lookup for + // regular/bold/italic/bolditalic with fixed offsets and a + // static decision table for fallback between these values. + + // TODO(Issue #190): if not in the fast path above, do + // expensive matching of weights, etc. + for template in self.templates.mut_iter() { + let maybe_template = template.get_if_matches(fctx, desc); + if maybe_template.is_some() { + return maybe_template; + } + } + + // If a request is made for a font family that exists, + // pick the first valid font in the family if we failed + // to find an exact match for the descriptor. + for template in self.templates.mut_iter() { + let maybe_template = template.get(); + if maybe_template.is_some() { + return maybe_template; + } + } + + None + } + + fn add_template(&mut self, identifier: &str, maybe_data: Option<Vec<u8>>) { + for template in self.templates.iter() { + if template.identifier() == identifier { + return; + } + } + + let template = FontTemplate::new(identifier, maybe_data); + self.templates.push(template); + } +} + +/// Commands that the FontContext sends to the font cache task. +pub enum Command { + GetFontTemplate(String, FontTemplateDescriptor, Sender<Reply>), + AddWebFont(String, Url, Sender<()>), + Exit(Sender<()>), +} + +/// Reply messages sent from the font cache task to the FontContext caller. +pub enum Reply { + GetFontTemplateReply(Arc<FontTemplateData>), +} + +/// The font cache task itself. It maintains a list of reference counted +/// font templates that are currently in use. +struct FontCache { + port: Receiver<Command>, + generic_fonts: HashMap<String, String>, + local_families: HashMap<String, FontFamily>, + web_families: HashMap<String, FontFamily>, + font_context: FontContextHandle, + resource_task: ResourceTask, +} + +impl FontCache { + fn run(&mut self) { + loop { + let msg = self.port.recv(); + + match msg { + GetFontTemplate(family, descriptor, result) => { + let maybe_font_template = self.get_font_template(&family, &descriptor); + let font_template = match maybe_font_template { + Some(font_template) => font_template, + None => self.get_last_resort_template(&descriptor), + }; + + result.send(GetFontTemplateReply(font_template)); + } + AddWebFont(family_name, url, result) => { + let maybe_resource = load_whole_resource(&self.resource_task, url.clone()); + match maybe_resource { + Ok((_, bytes)) => { + if !self.web_families.contains_key(&family_name) { + let family = FontFamily::new(); + self.web_families.insert(family_name.clone(), family); + } + let family = self.web_families.get_mut(&family_name); + family.add_template(format!("{}", url).as_slice(), Some(bytes)); + }, + Err(msg) => { + fail!("{}: url={}", msg, url); + } + } + result.send(()); + } + Exit(result) => { + result.send(()); + break; + } + } + } + } + + fn refresh_local_families(&mut self) { + self.local_families.clear(); + get_available_families(|family_name| { + if !self.local_families.contains_key(&family_name) { + let family = FontFamily::new(); + self.local_families.insert(family_name, family); + } + }); + } + + fn transform_family(&self, family: &String) -> String { + match self.generic_fonts.find(family) { + None => family.to_string(), + Some(mapped_family) => (*mapped_family).clone() + } + } + + fn find_font_in_local_family<'a>(&'a mut self, family_name: &String, desc: &FontTemplateDescriptor) + -> Option<Arc<FontTemplateData>> { + // TODO(Issue #188): look up localized font family names if canonical name not found + // look up canonical name + if self.local_families.contains_key(family_name) { + debug!("FontList: Found font family with name={:s}", family_name.to_string()); + let s = self.local_families.get_mut(family_name); + + if s.templates.len() == 0 { + get_variations_for_family(family_name.as_slice(), |path| { + s.add_template(path.as_slice(), None); + }); + } + + // TODO(Issue #192: handle generic font families, like 'serif' and 'sans-serif'. + // if such family exists, try to match style to a font + let result = s.find_font_for_style(desc, &self.font_context); + if result.is_some() { + return result; + } + + None + } else { + debug!("FontList: Couldn't find font family with name={:s}", family_name.to_string()); + None + } + } + + fn find_font_in_web_family<'a>(&'a mut self, family_name: &String, desc: &FontTemplateDescriptor) + -> Option<Arc<FontTemplateData>> { + if self.web_families.contains_key(family_name) { + let family = self.web_families.get_mut(family_name); + let maybe_font = family.find_font_for_style(desc, &self.font_context); + maybe_font + } else { + None + } + } + + fn get_font_template(&mut self, family: &String, desc: &FontTemplateDescriptor) -> Option<Arc<FontTemplateData>> { + let transformed_family_name = self.transform_family(family); + let mut maybe_template = self.find_font_in_web_family(&transformed_family_name, desc); + if maybe_template.is_none() { + maybe_template = self.find_font_in_local_family(&transformed_family_name, desc); + } + maybe_template + } + + fn get_last_resort_template(&mut self, desc: &FontTemplateDescriptor) -> Arc<FontTemplateData> { + let last_resort = get_last_resort_font_families(); + + for family in last_resort.iter() { + let maybe_font_in_family = self.find_font_in_local_family(family, desc); + if maybe_font_in_family.is_some() { + return maybe_font_in_family.unwrap(); + } + } + + fail!("Unable to find any fonts that match (do you have fallback fonts installed?)"); + } +} + +/// The public interface to the font cache task, used exclusively by +/// the per-thread/task FontContext structures. +#[deriving(Clone)] +pub struct FontCacheTask { + chan: Sender<Command>, +} + +impl FontCacheTask { + pub fn new(resource_task: ResourceTask) -> FontCacheTask { + let (chan, port) = channel(); + + spawn(proc() { + // TODO: Allow users to specify these. + let mut generic_fonts = HashMap::with_capacity(5); + generic_fonts.insert("serif".to_string(), "Times New Roman".to_string()); + generic_fonts.insert("sans-serif".to_string(), "Arial".to_string()); + generic_fonts.insert("cursive".to_string(), "Apple Chancery".to_string()); + generic_fonts.insert("fantasy".to_string(), "Papyrus".to_string()); + generic_fonts.insert("monospace".to_string(), "Menlo".to_string()); + + let mut cache = FontCache { + port: port, + generic_fonts: generic_fonts, + local_families: HashMap::new(), + web_families: HashMap::new(), + font_context: FontContextHandle::new(), + resource_task: resource_task, + }; + + cache.refresh_local_families(); + cache.run(); + }); + + FontCacheTask { + chan: chan, + } + } + + pub fn get_font_template(&self, family: String, desc: FontTemplateDescriptor) + -> Arc<FontTemplateData> { + + let (response_chan, response_port) = channel(); + self.chan.send(GetFontTemplate(family, desc, response_chan)); + + let reply = response_port.recv(); + + match reply { + GetFontTemplateReply(data) => { + data + } + } + } + + pub fn add_web_font(&self, family: String, url: Url) { + let (response_chan, response_port) = channel(); + self.chan.send(AddWebFont(family, url, response_chan)); + response_port.recv(); + } + + pub fn exit(&self) { + let (response_chan, response_port) = channel(); + self.chan.send(Exit(response_chan)); + response_port.recv(); + } +} diff --git a/components/gfx/font_context.rs b/components/gfx/font_context.rs new file mode 100644 index 00000000000..0a1ef69e0ce --- /dev/null +++ b/components/gfx/font_context.rs @@ -0,0 +1,148 @@ +/* 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 font::{Font, FontGroup}; +use font::SpecifiedFontStyle; +use platform::font_context::FontContextHandle; +use style::computed_values::font_style; + +use font_cache_task::FontCacheTask; +use font_template::FontTemplateDescriptor; +use platform::font_template::FontTemplateData; +use font::FontHandleMethods; +use platform::font::FontHandle; +use servo_util::cache::HashCache; + +use std::rc::{Rc, Weak}; +use std::cell::RefCell; +use sync::Arc; + +use azure::AzFloat; +use azure::azure_hl::BackendType; +use azure::scaled_font::ScaledFont; + +#[cfg(target_os="linux")] +#[cfg(target_os="android")] +use azure::scaled_font::FontData; + +#[cfg(target_os="linux")] +#[cfg(target_os="android")] +fn create_scaled_font(backend: BackendType, template: &Arc<FontTemplateData>, pt_size: f64) -> ScaledFont { + ScaledFont::new(backend, FontData(&template.bytes), pt_size as AzFloat) +} + +#[cfg(target_os="macos")] +fn create_scaled_font(backend: BackendType, template: &Arc<FontTemplateData>, pt_size: f64) -> ScaledFont { + let cgfont = template.ctfont.get_ref().copy_to_CGFont(); + ScaledFont::new(backend, &cgfont, pt_size as AzFloat) +} + +/// A cached azure font (per render task) that +/// can be shared by multiple text runs. +struct RenderFontCacheEntry { + pt_size: f64, + identifier: String, + font: Rc<RefCell<ScaledFont>>, +} + +/// The FontContext represents the per-thread/task state necessary for +/// working with fonts. It is the public API used by the layout and +/// render code. It talks directly to the font cache task where +/// required. +pub struct FontContext { + platform_handle: FontContextHandle, + font_cache_task: FontCacheTask, + + /// Weak reference as the layout FontContext is persistent. + layout_font_cache: Vec<Weak<RefCell<Font>>>, + + /// Strong reference as the render FontContext is (for now) recycled + /// per frame. TODO: Make this weak when incremental redraw is done. + render_font_cache: Vec<RenderFontCacheEntry>, +} + +impl FontContext { + pub fn new(font_cache_task: FontCacheTask) -> FontContext { + let handle = FontContextHandle::new(); + FontContext { + platform_handle: handle, + font_cache_task: font_cache_task, + layout_font_cache: vec!(), + render_font_cache: vec!(), + } + } + + /// Create a font for use in layout calculations. + fn create_layout_font(&self, template: Arc<FontTemplateData>, + descriptor: FontTemplateDescriptor, pt_size: f64) -> Font { + + let handle: FontHandle = FontHandleMethods::new_from_template(&self.platform_handle, template, Some(pt_size)).unwrap(); + let metrics = handle.get_metrics(); + + Font { + handle: handle, + shaper: None, + descriptor: descriptor, + pt_size: pt_size, + metrics: metrics, + shape_cache: HashCache::new(), + glyph_advance_cache: HashCache::new(), + } + } + + /// Create a group of fonts for use in layout calculations. May return + /// a cached font if this font instance has already been used by + /// this context. + pub fn get_layout_font_group_for_style(&mut self, style: &SpecifiedFontStyle) -> FontGroup { + // Remove all weak pointers that have been dropped. + self.layout_font_cache.retain(|maybe_font| { + maybe_font.upgrade().is_some() + }); + + let mut fonts: Vec<Rc<RefCell<Font>>> = vec!(); + + for family in style.families.iter() { + let desc = FontTemplateDescriptor::new(style.weight, style.style == font_style::italic); + + // GWTODO: Check on real pages if this is faster as Vec() or HashMap(). + let mut cache_hit = false; + for maybe_cached_font in self.layout_font_cache.iter() { + let cached_font = maybe_cached_font.upgrade().unwrap(); + if cached_font.borrow().descriptor == desc { + fonts.push(cached_font.clone()); + cache_hit = true; + break; + } + } + + if !cache_hit { + let font_template = self.font_cache_task.get_font_template(family.clone(), desc.clone()); + let layout_font = Rc::new(RefCell::new(self.create_layout_font(font_template, desc.clone(), style.pt_size))); + self.layout_font_cache.push(layout_font.downgrade()); + fonts.push(layout_font); + } + } + + FontGroup::new(fonts) + } + + /// Create a render font for use with azure. May return a cached + /// reference if already used by this font context. + pub fn get_render_font_from_template(&mut self, template: &Arc<FontTemplateData>, pt_size: f64, backend: BackendType) -> Rc<RefCell<ScaledFont>> { + for cached_font in self.render_font_cache.iter() { + if cached_font.pt_size == pt_size && + cached_font.identifier == template.identifier { + return cached_font.font.clone(); + } + } + + let render_font = Rc::new(RefCell::new(create_scaled_font(backend, template, pt_size))); + self.render_font_cache.push(RenderFontCacheEntry{ + font: render_font.clone(), + pt_size: pt_size, + identifier: template.identifier.clone(), + }); + render_font + } +} diff --git a/components/gfx/font_template.rs b/components/gfx/font_template.rs new file mode 100644 index 00000000000..3f4916b69c5 --- /dev/null +++ b/components/gfx/font_template.rs @@ -0,0 +1,157 @@ +/* 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 style::computed_values::font_weight; +use platform::font_context::FontContextHandle; +use platform::font::FontHandle; +use platform::font_template::FontTemplateData; + +use sync::{Arc, Weak}; +use font::FontHandleMethods; + +/// Describes how to select a font from a given family. +/// This is very basic at the moment and needs to be +/// expanded or refactored when we support more of the +/// font styling parameters. +#[deriving(Clone)] +pub struct FontTemplateDescriptor { + pub weight: font_weight::T, + pub italic: bool, +} + +impl FontTemplateDescriptor { + pub fn new(weight: font_weight::T, italic: bool) -> FontTemplateDescriptor { + FontTemplateDescriptor { + weight: weight, + italic: italic, + } + } +} + +impl PartialEq for FontTemplateDescriptor { + fn eq(&self, other: &FontTemplateDescriptor) -> bool { + self.weight.is_bold() == other.weight.is_bold() && + self.italic == other.italic + } +} + +/// This describes all the information needed to create +/// font instance handles. It contains a unique +/// FontTemplateData structure that is platform specific. +pub struct FontTemplate { + identifier: String, + descriptor: Option<FontTemplateDescriptor>, + weak_ref: Option<Weak<FontTemplateData>>, + strong_ref: Option<Arc<FontTemplateData>>, // GWTODO: Add code path to unset the strong_ref for web fonts! + is_valid: bool, +} + +/// Holds all of the template information for a font that +/// is common, regardless of the number of instances of +/// this font handle per thread. +impl FontTemplate { + pub fn new(identifier: &str, maybe_bytes: Option<Vec<u8>>) -> FontTemplate { + let maybe_data = match maybe_bytes { + Some(_) => Some(FontTemplateData::new(identifier, maybe_bytes)), + None => None, + }; + + let maybe_strong_ref = match maybe_data { + Some(data) => Some(Arc::new(data)), + None => None, + }; + + let maybe_weak_ref = match maybe_strong_ref { + Some(ref strong_ref) => Some(strong_ref.downgrade()), + None => None, + }; + + FontTemplate { + identifier: identifier.to_string(), + descriptor: None, + weak_ref: maybe_weak_ref, + strong_ref: maybe_strong_ref, + is_valid: true, + } + } + + pub fn identifier<'a>(&'a self) -> &'a str { + self.identifier.as_slice() + } + + /// Get the data for creating a font if it matches a given descriptor. + pub fn get_if_matches(&mut self, fctx: &FontContextHandle, + requested_desc: &FontTemplateDescriptor) -> Option<Arc<FontTemplateData>> { + // The font template data can be unloaded when nothing is referencing + // it (via the Weak reference to the Arc above). However, if we have + // already loaded a font, store the style information about it separately, + // so that we can do font matching against it again in the future + // without having to reload the font (unless it is an actual match). + match self.descriptor { + Some(actual_desc) => { + if *requested_desc == actual_desc { + Some(self.get_data()) + } else { + None + } + }, + None => { + if self.is_valid { + let data = self.get_data(); + let handle: Result<FontHandle, ()> = FontHandleMethods::new_from_template(fctx, data.clone(), None); + match handle { + Ok(handle) => { + let actual_desc = FontTemplateDescriptor::new(handle.boldness(), + handle.is_italic()); + let desc_match = actual_desc == *requested_desc; + + self.descriptor = Some(actual_desc); + self.is_valid = true; + if desc_match { + Some(data) + } else { + None + } + } + Err(()) => { + self.is_valid = false; + debug!("Unable to create a font from template {}", self.identifier); + None + } + } + } else { + None + } + } + } + } + + /// Get the data for creating a font. + pub fn get(&mut self) -> Option<Arc<FontTemplateData>> { + match self.is_valid { + true => Some(self.get_data()), + false => None + } + } + + /// Get the font template data. If any strong references still + /// exist, it will return a clone, otherwise it will load the + /// font data and store a weak reference to it internally. + pub fn get_data(&mut self) -> Arc<FontTemplateData> { + let maybe_data = match self.weak_ref { + Some(ref data) => data.upgrade(), + None => None, + }; + + match maybe_data { + Some(data) => data, + None => { + assert!(self.strong_ref.is_none()); + let template_data = Arc::new(FontTemplateData::new(self.identifier.as_slice(), None)); + self.weak_ref = Some(template_data.downgrade()); + template_data + } + } + } +} diff --git a/components/gfx/lib.rs b/components/gfx/lib.rs new file mode 100644 index 00000000000..838b20f71f8 --- /dev/null +++ b/components/gfx/lib.rs @@ -0,0 +1,72 @@ +/* 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/. */ + +#![feature(globs, macro_rules, phase, unsafe_destructor)] + +#![feature(phase)] +#[phase(plugin, link)] +extern crate log; + +extern crate debug; +extern crate azure; +extern crate collections; +extern crate geom; +extern crate layers; +extern crate libc; +extern crate native; +extern crate rustrt; +extern crate stb_image; +extern crate png; +extern crate serialize; +#[phase(plugin)] +extern crate servo_macros = "macros"; +extern crate servo_net = "net"; +#[phase(plugin, link)] +extern crate servo_util = "util"; +extern crate servo_msg = "msg"; +extern crate style; +extern crate sync; +extern crate url; + +// Eventually we would like the shaper to be pluggable, as many operating systems have their own +// shapers. For now, however, this is a hard dependency. +extern crate harfbuzz; + +// Linux and Android-specific library dependencies +#[cfg(target_os="linux")] #[cfg(target_os="android")] extern crate fontconfig; +#[cfg(target_os="linux")] #[cfg(target_os="android")] extern crate freetype; + +// Mac OS-specific library dependencies +#[cfg(target_os="macos")] extern crate core_foundation; +#[cfg(target_os="macos")] extern crate core_graphics; +#[cfg(target_os="macos")] extern crate core_text; + +pub use render_context::RenderContext; + +// Private rendering modules +mod render_context; + +// Rendering +pub mod color; +#[path="display_list/mod.rs"] +pub mod display_list; +pub mod render_task; + +// Fonts +pub mod font; +pub mod font_context; +pub mod font_cache_task; +pub mod font_template; + +// Misc. +mod buffer_map; + +// Platform-specific implementations. +#[path="platform/mod.rs"] +pub mod platform; + +// Text +#[path = "text/mod.rs"] +pub mod text; + diff --git a/components/gfx/platform/freetype/font.rs b/components/gfx/platform/freetype/font.rs new file mode 100644 index 00000000000..7e58b850e2b --- /dev/null +++ b/components/gfx/platform/freetype/font.rs @@ -0,0 +1,297 @@ +/* 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/. */ + +extern crate freetype; + +use font::{FontHandleMethods, FontMetrics, FontTableMethods}; +use font::{FontTableTag, FractionalPixel}; +use servo_util::geometry::Au; +use servo_util::geometry; +use platform::font_context::FontContextHandle; +use text::glyph::GlyphId; +use text::util::{float_to_fixed, fixed_to_float}; +use style::computed_values::font_weight; +use platform::font_template::FontTemplateData; + +use freetype::freetype::{FT_Get_Char_Index, FT_Get_Postscript_Name}; +use freetype::freetype::{FT_Load_Glyph, FT_Set_Char_Size}; +use freetype::freetype::{FT_Get_Kerning, FT_Get_Sfnt_Table}; +use freetype::freetype::{FT_New_Memory_Face, FT_Done_Face}; +use freetype::freetype::{FTErrorMethods, FT_F26Dot6, FT_Face, FT_FaceRec}; +use freetype::freetype::{FT_GlyphSlot, FT_Library, FT_Long, FT_ULong}; +use freetype::freetype::{FT_KERNING_DEFAULT, FT_STYLE_FLAG_ITALIC, FT_STYLE_FLAG_BOLD}; +use freetype::freetype::{FT_SizeRec, FT_UInt, FT_Size_Metrics, struct_FT_Vector_}; +use freetype::freetype::{ft_sfnt_os2}; +use freetype::tt_os2::TT_OS2; + +use std::mem; +use std::ptr; +use std::string; + +use sync::Arc; + +fn float_to_fixed_ft(f: f64) -> i32 { + float_to_fixed(6, f) +} + +fn fixed_to_float_ft(f: i32) -> f64 { + fixed_to_float(6, f) +} + +pub struct FontTable; + +impl FontTableMethods for FontTable { + fn with_buffer(&self, _blk: |*const u8, uint|) { + fail!() + } +} + +pub struct FontHandle { + // The font binary. This must stay valid for the lifetime of the font, + // if the font is created using FT_Memory_Face. + pub font_data: Arc<FontTemplateData>, + pub face: FT_Face, + pub handle: FontContextHandle +} + +#[unsafe_destructor] +impl Drop for FontHandle { + fn drop(&mut self) { + assert!(self.face.is_not_null()); + unsafe { + if !FT_Done_Face(self.face).succeeded() { + fail!("FT_Done_Face failed"); + } + } + } +} + +impl FontHandleMethods for FontHandle { + fn new_from_template(fctx: &FontContextHandle, + template: Arc<FontTemplateData>, + pt_size: Option<f64>) + -> Result<FontHandle, ()> { + let ft_ctx: FT_Library = fctx.ctx.ctx; + if ft_ctx.is_null() { return Err(()); } + + let bytes = &template.deref().bytes; + let face_result = create_face_from_buffer(ft_ctx, bytes.as_ptr(), bytes.len(), pt_size); + + // TODO: this could be more simply written as result::chain + // and moving buf into the struct ctor, but cant' move out of + // captured binding. + return match face_result { + Ok(face) => { + let handle = FontHandle { + face: face, + font_data: template.clone(), + handle: fctx.clone() + }; + Ok(handle) + } + Err(()) => Err(()) + }; + + fn create_face_from_buffer(lib: FT_Library, cbuf: *const u8, cbuflen: uint, pt_size: Option<f64>) + -> Result<FT_Face, ()> { + unsafe { + let mut face: FT_Face = ptr::mut_null(); + let face_index = 0 as FT_Long; + let result = FT_New_Memory_Face(lib, cbuf, cbuflen as FT_Long, + face_index, &mut face); + + if !result.succeeded() || face.is_null() { + return Err(()); + } + match pt_size { + Some(s) => { + match FontHandle::set_char_size(face, s) { + Ok(_) => Ok(face), + Err(_) => Err(()), + } + } + None => Ok(face), + } + } + } + } + fn get_template(&self) -> Arc<FontTemplateData> { + self.font_data.clone() + } + fn family_name(&self) -> String { + unsafe { string::raw::from_buf(&*(*self.face).family_name as *const i8 as *const u8) } + } + fn face_name(&self) -> String { + unsafe { string::raw::from_buf(&*FT_Get_Postscript_Name(self.face) as *const i8 as *const u8) } + } + fn is_italic(&self) -> bool { + unsafe { (*self.face).style_flags & FT_STYLE_FLAG_ITALIC != 0 } + } + fn boldness(&self) -> font_weight::T { + let default_weight = font_weight::Weight400; + if unsafe { (*self.face).style_flags & FT_STYLE_FLAG_BOLD == 0 } { + default_weight + } else { + unsafe { + let os2 = FT_Get_Sfnt_Table(self.face, ft_sfnt_os2) as *mut TT_OS2; + let valid = os2.is_not_null() && (*os2).version != 0xffff; + if valid { + let weight =(*os2).usWeightClass; + match weight { + 1 | 100..199 => font_weight::Weight100, + 2 | 200..299 => font_weight::Weight200, + 3 | 300..399 => font_weight::Weight300, + 4 | 400..499 => font_weight::Weight400, + 5 | 500..599 => font_weight::Weight500, + 6 | 600..699 => font_weight::Weight600, + 7 | 700..799 => font_weight::Weight700, + 8 | 800..899 => font_weight::Weight800, + 9 | 900..999 => font_weight::Weight900, + _ => default_weight + } + } else { + default_weight + } + } + } + } + + fn glyph_index(&self, + codepoint: char) -> Option<GlyphId> { + assert!(self.face.is_not_null()); + unsafe { + let idx = FT_Get_Char_Index(self.face, codepoint as FT_ULong); + return if idx != 0 as FT_UInt { + Some(idx as GlyphId) + } else { + debug!("Invalid codepoint: {}", codepoint); + None + }; + } + } + + fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) + -> FractionalPixel { + assert!(self.face.is_not_null()); + let mut delta = struct_FT_Vector_ { x: 0, y: 0 }; + unsafe { + FT_Get_Kerning(self.face, first_glyph, second_glyph, FT_KERNING_DEFAULT, &mut delta); + } + fixed_to_float_ft(delta.x as i32) + } + + fn glyph_h_advance(&self, + glyph: GlyphId) -> Option<FractionalPixel> { + assert!(self.face.is_not_null()); + unsafe { + let res = FT_Load_Glyph(self.face, glyph as FT_UInt, 0); + if res.succeeded() { + let void_glyph = (*self.face).glyph; + let slot: FT_GlyphSlot = mem::transmute(void_glyph); + assert!(slot.is_not_null()); + debug!("metrics: {:?}", (*slot).metrics); + let advance = (*slot).metrics.horiAdvance; + debug!("h_advance for {} is {}", glyph, advance); + let advance = advance as i32; + return Some(fixed_to_float_ft(advance) as FractionalPixel); + } else { + debug!("Unable to load glyph {}. reason: {}", glyph, res); + return None; + } + } + } + + fn get_metrics(&self) -> FontMetrics { + /* TODO(Issue #76): complete me */ + let face = self.get_face_rec(); + + let underline_size = self.font_units_to_au(face.underline_thickness as f64); + let underline_offset = self.font_units_to_au(face.underline_position as f64); + let em_size = self.font_units_to_au(face.units_per_EM as f64); + let ascent = self.font_units_to_au(face.ascender as f64); + let descent = self.font_units_to_au(face.descender as f64); + let max_advance = self.font_units_to_au(face.max_advance_width as f64); + + // 'leading' is supposed to be the vertical distance between two baselines, + // reflected by the height attibute in freetype. On OS X (w/ CTFont), + // leading represents the distance between the bottom of a line descent to + // the top of the next line's ascent or: (line_height - ascent - descent), + // see http://stackoverflow.com/a/5635981 for CTFont implementation. + // Convert using a formular similar to what CTFont returns for consistency. + let height = self.font_units_to_au(face.height as f64); + let leading = height - (ascent + descent); + + let mut strikeout_size = geometry::from_pt(0.0); + let mut strikeout_offset = geometry::from_pt(0.0); + let mut x_height = geometry::from_pt(0.0); + unsafe { + let os2 = FT_Get_Sfnt_Table(face, ft_sfnt_os2) as *mut TT_OS2; + let valid = os2.is_not_null() && (*os2).version != 0xffff; + if valid { + strikeout_size = self.font_units_to_au((*os2).yStrikeoutSize as f64); + strikeout_offset = self.font_units_to_au((*os2).yStrikeoutPosition as f64); + x_height = self.font_units_to_au((*os2).sxHeight as f64); + } + } + + let metrics = FontMetrics { + underline_size: underline_size, + underline_offset: underline_offset, + strikeout_size: strikeout_size, + strikeout_offset: strikeout_offset, + leading: leading, + x_height: x_height, + em_size: em_size, + ascent: ascent, + descent: -descent, // linux font's seem to use the opposite sign from mac + max_advance: max_advance, + line_gap: height, + }; + + debug!("Font metrics (@{:f} pt): {:?}", geometry::to_pt(em_size), metrics); + return metrics; + } + + fn get_table_for_tag(&self, _: FontTableTag) -> Option<FontTable> { + None + } +} + +impl<'a> FontHandle { + fn set_char_size(face: FT_Face, pt_size: f64) -> Result<(), ()>{ + let char_width = float_to_fixed_ft(pt_size) as FT_F26Dot6; + let char_height = float_to_fixed_ft(pt_size) as FT_F26Dot6; + let h_dpi = 72; + let v_dpi = 72; + + unsafe { + let result = FT_Set_Char_Size(face, char_width, char_height, h_dpi, v_dpi); + if result.succeeded() { Ok(()) } else { Err(()) } + } + } + + fn get_face_rec(&'a self) -> &'a mut FT_FaceRec { + unsafe { + &mut (*self.face) + } + } + + fn font_units_to_au(&self, value: f64) -> Au { + let face = self.get_face_rec(); + + // face.size is a *c_void in the bindings, presumably to avoid + // recursive structural types + let size: &FT_SizeRec = unsafe { mem::transmute(&(*face.size)) }; + let metrics: &FT_Size_Metrics = &(*size).metrics; + + let em_size = face.units_per_EM as f64; + let x_scale = (metrics.x_ppem as f64) / em_size as f64; + + // If this isn't true then we're scaling one of the axes wrong + assert!(metrics.x_ppem == metrics.y_ppem); + + return geometry::from_frac_px(value * x_scale); + } +} + diff --git a/components/gfx/platform/freetype/font_context.rs b/components/gfx/platform/freetype/font_context.rs new file mode 100644 index 00000000000..b6e8222dc61 --- /dev/null +++ b/components/gfx/platform/freetype/font_context.rs @@ -0,0 +1,82 @@ +/* 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 freetype::freetype::FTErrorMethods; +use freetype::freetype::FT_Add_Default_Modules; +use freetype::freetype::FT_Done_FreeType; +use freetype::freetype::FT_Library; +use freetype::freetype::FT_Memory; +use freetype::freetype::FT_New_Library; +use freetype::freetype::struct_FT_MemoryRec_; + +use std::ptr; +use std::rc::Rc; + +use libc; +use libc::{c_void, c_long, size_t, malloc}; +use std::mem; + +extern fn ft_alloc(_mem: FT_Memory, size: c_long) -> *mut c_void { + unsafe { + let ptr = libc::malloc(size as size_t); + ptr as *mut c_void + } +} + +extern fn ft_free(_mem: FT_Memory, block: *mut c_void) { + unsafe { + libc::free(block); + } +} + +extern fn ft_realloc(_mem: FT_Memory, _cur_size: c_long, new_size: c_long, block: *mut c_void) -> *mut c_void { + unsafe { + let ptr = libc::realloc(block, new_size as size_t); + ptr as *mut c_void + } +} + +#[deriving(Clone)] +pub struct FreeTypeLibraryHandle { + pub ctx: FT_Library, +} + +#[deriving(Clone)] +pub struct FontContextHandle { + pub ctx: Rc<FreeTypeLibraryHandle>, +} + +impl Drop for FreeTypeLibraryHandle { + fn drop(&mut self) { + assert!(self.ctx.is_not_null()); + unsafe { FT_Done_FreeType(self.ctx) }; + } +} + +impl FontContextHandle { + pub fn new() -> FontContextHandle { + unsafe { + + let ptr = libc::malloc(mem::size_of::<struct_FT_MemoryRec_>() as size_t); + let allocator: &mut struct_FT_MemoryRec_ = mem::transmute(ptr); + ptr::write(allocator, struct_FT_MemoryRec_ { + user: ptr::mut_null(), + alloc: ft_alloc, + free: ft_free, + realloc: ft_realloc, + }); + + let mut ctx: FT_Library = ptr::mut_null(); + + let result = FT_New_Library(ptr as FT_Memory, &mut ctx); + if !result.succeeded() { fail!("Unable to initialize FreeType library"); } + + FT_Add_Default_Modules(ctx); + + FontContextHandle { + ctx: Rc::new(FreeTypeLibraryHandle { ctx: ctx }), + } + } + } +} diff --git a/components/gfx/platform/freetype/font_list.rs b/components/gfx/platform/freetype/font_list.rs new file mode 100644 index 00000000000..87ce446381d --- /dev/null +++ b/components/gfx/platform/freetype/font_list.rs @@ -0,0 +1,115 @@ +/* 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/. */ + +#![allow(uppercase_variables)] + +extern crate freetype; +extern crate fontconfig; + +use fontconfig::fontconfig::{FcChar8, FcResultMatch, FcSetSystem}; +use fontconfig::fontconfig::{ + FcConfigGetCurrent, FcConfigGetFonts, FcPatternGetString, + FcPatternDestroy, FcFontSetDestroy, + FcPatternCreate, FcPatternAddString, + FcFontSetList, FcObjectSetCreate, FcObjectSetDestroy, + FcObjectSetAdd, FcPatternGetInteger +}; + +use libc; +use libc::c_int; +use std::ptr; +use std::string; + +pub fn get_available_families(callback: |String|) { + unsafe { + let config = FcConfigGetCurrent(); + let fontSet = FcConfigGetFonts(config, FcSetSystem); + for i in range(0, (*fontSet).nfont as int) { + let font = (*fontSet).fonts.offset(i); + let mut family: *mut FcChar8 = ptr::mut_null(); + let mut v: c_int = 0; + let mut FC_FAMILY_C = "family".to_c_str(); + let FC_FAMILY = FC_FAMILY_C.as_mut_ptr(); + while FcPatternGetString(*font, FC_FAMILY, v, &mut family) == FcResultMatch { + let family_name = string::raw::from_buf(family as *const i8 as *const u8); + callback(family_name); + v += 1; + } + } + } +} + +pub fn get_variations_for_family(family_name: &str, callback: |String|) { + debug!("getting variations for {}", family_name); + unsafe { + let config = FcConfigGetCurrent(); + let mut font_set = FcConfigGetFonts(config, FcSetSystem); + let font_set_array_ptr = &mut font_set; + let pattern = FcPatternCreate(); + assert!(pattern.is_not_null()); + let mut FC_FAMILY_C = "family".to_c_str(); + let FC_FAMILY = FC_FAMILY_C.as_mut_ptr(); + let mut family_name_c = family_name.to_c_str(); + let family_name = family_name_c.as_mut_ptr(); + let ok = FcPatternAddString(pattern, FC_FAMILY, family_name as *mut FcChar8); + assert!(ok != 0); + + let object_set = FcObjectSetCreate(); + assert!(object_set.is_not_null()); + + let mut FC_FILE_C = "file".to_c_str(); + let FC_FILE = FC_FILE_C.as_mut_ptr(); + FcObjectSetAdd(object_set, FC_FILE); + let mut FC_INDEX_C = "index".to_c_str(); + let FC_INDEX = FC_INDEX_C.as_mut_ptr(); + FcObjectSetAdd(object_set, FC_INDEX); + + let matches = FcFontSetList(config, font_set_array_ptr, 1, pattern, object_set); + + debug!("found {} variations", (*matches).nfont); + + for i in range(0, (*matches).nfont as int) { + let font = (*matches).fonts.offset(i); + let mut FC_FILE_C = "file".to_c_str(); + let FC_FILE = FC_FILE_C.as_mut_ptr(); + let mut file: *mut FcChar8 = ptr::mut_null(); + let file = if FcPatternGetString(*font, FC_FILE, 0, &mut file) == FcResultMatch { + string::raw::from_buf(file as *const i8 as *const u8) + } else { + fail!(); + }; + let mut FC_INDEX_C = "index".to_c_str(); + let FC_INDEX = FC_INDEX_C.as_mut_ptr(); + let mut index: libc::c_int = 0; + let index = if FcPatternGetInteger(*font, FC_INDEX, 0, &mut index) == FcResultMatch { + index + } else { + fail!(); + }; + + debug!("variation file: {}", file); + debug!("variation index: {}", index); + + callback(file); + } + + FcFontSetDestroy(matches); + FcPatternDestroy(pattern); + FcObjectSetDestroy(object_set); + } +} + +#[cfg(target_os="linux")] +pub fn get_last_resort_font_families() -> Vec<String> { + vec!( + "Fira Sans".to_string(), + "DejaVu Sans".to_string(), + "Arial".to_string() + ) +} + +#[cfg(target_os="android")] +pub fn get_last_resort_font_families() -> Vec<String> { + vec!("Roboto".to_string()) +} diff --git a/components/gfx/platform/freetype/font_template.rs b/components/gfx/platform/freetype/font_template.rs new file mode 100644 index 00000000000..663ea64ab29 --- /dev/null +++ b/components/gfx/platform/freetype/font_template.rs @@ -0,0 +1,35 @@ +/* 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 std::io; +use std::io::File; + +/// Platform specific font representation for Linux. +/// The identifier is an absolute path, and the bytes +/// field is the loaded data that can be passed to +/// freetype and azure directly. +pub struct FontTemplateData { + pub bytes: Vec<u8>, + pub identifier: String, +} + +impl FontTemplateData { + pub fn new(identifier: &str, font_data: Option<Vec<u8>>) -> FontTemplateData { + let bytes = match font_data { + Some(bytes) => { + bytes + }, + None => { + // TODO: Handle file load failure! + let mut file = File::open_mode(&Path::new(identifier), io::Open, io::Read).unwrap(); + file.read_to_end().unwrap() + }, + }; + + FontTemplateData { + bytes: bytes, + identifier: identifier.to_string(), + } + } +} diff --git a/components/gfx/platform/macos/font.rs b/components/gfx/platform/macos/font.rs new file mode 100644 index 00000000000..f616ef328bd --- /dev/null +++ b/components/gfx/platform/macos/font.rs @@ -0,0 +1,185 @@ +/* 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/. */ + +/// Implementation of Quartz (CoreGraphics) fonts. + +extern crate core_foundation; +extern crate core_graphics; +extern crate core_text; + +use font::{FontHandleMethods, FontMetrics, FontTableMethods}; +use font::FontTableTag; +use font::FractionalPixel; +use servo_util::geometry::{Au, px_to_pt}; +use servo_util::geometry; +use platform::macos::font_context::FontContextHandle; +use text::glyph::GlyphId; +use style::computed_values::font_weight; +use platform::font_template::FontTemplateData; + +use core_foundation::base::CFIndex; +use core_foundation::data::CFData; +use core_foundation::string::UniChar; +use core_graphics::font::CGGlyph; +use core_graphics::geometry::CGRect; +use core_text::font::CTFont; +use core_text::font_descriptor::{SymbolicTraitAccessors, TraitAccessors}; +use core_text::font_descriptor::{kCTFontDefaultOrientation}; + +use std::ptr; +use sync::Arc; + +pub struct FontTable { + data: CFData, +} + +// Noncopyable. +impl Drop for FontTable { + fn drop(&mut self) {} +} + +impl FontTable { + pub fn wrap(data: CFData) -> FontTable { + FontTable { data: data } + } +} + +impl FontTableMethods for FontTable { + fn with_buffer(&self, blk: |*const u8, uint|) { + blk(self.data.bytes().as_ptr(), self.data.len() as uint); + } +} + +pub struct FontHandle { + pub font_data: Arc<FontTemplateData>, + pub ctfont: CTFont, +} + +impl FontHandleMethods for FontHandle { + fn new_from_template(_fctx: &FontContextHandle, + template: Arc<FontTemplateData>, + pt_size: Option<f64>) + -> Result<FontHandle, ()> { + let size = match pt_size { + Some(s) => s, + None => 0.0 + }; + match template.ctfont { + Some(ref ctfont) => { + Ok(FontHandle { + font_data: template.clone(), + ctfont: ctfont.clone_with_font_size(size), + }) + } + None => { + Err(()) + } + } + } + + fn get_template(&self) -> Arc<FontTemplateData> { + self.font_data.clone() + } + + fn family_name(&self) -> String { + self.ctfont.family_name() + } + + fn face_name(&self) -> String { + self.ctfont.face_name() + } + + fn is_italic(&self) -> bool { + self.ctfont.symbolic_traits().is_italic() + } + + fn boldness(&self) -> font_weight::T { + // -1.0 to 1.0 + let normalized = self.ctfont.all_traits().normalized_weight(); + // 0.0 to 9.0 + let normalized = (normalized + 1.0) / 2.0 * 9.0; + if normalized < 1.0 { return font_weight::Weight100; } + if normalized < 2.0 { return font_weight::Weight200; } + if normalized < 3.0 { return font_weight::Weight300; } + if normalized < 4.0 { return font_weight::Weight400; } + if normalized < 5.0 { return font_weight::Weight500; } + if normalized < 6.0 { return font_weight::Weight600; } + if normalized < 7.0 { return font_weight::Weight700; } + if normalized < 8.0 { return font_weight::Weight800; } + return font_weight::Weight900; + } + + fn glyph_index(&self, codepoint: char) -> Option<GlyphId> { + let characters: [UniChar, ..1] = [codepoint as UniChar]; + let mut glyphs: [CGGlyph, ..1] = [0 as CGGlyph]; + let count: CFIndex = 1; + + let result = self.ctfont.get_glyphs_for_characters(&characters[0], + &mut glyphs[0], + count); + + if !result { + // No glyph for this character + return None; + } + + assert!(glyphs[0] != 0); // FIXME: error handling + return Some(glyphs[0] as GlyphId); + } + + fn glyph_h_kerning(&self, _first_glyph: GlyphId, _second_glyph: GlyphId) + -> FractionalPixel { + // TODO: Implement on mac + 0.0 + } + + fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> { + let glyphs = [glyph as CGGlyph]; + let advance = self.ctfont.get_advances_for_glyphs(kCTFontDefaultOrientation, + &glyphs[0], + ptr::mut_null(), + 1); + Some(advance as FractionalPixel) + } + + fn get_metrics(&self) -> FontMetrics { + let bounding_rect: CGRect = self.ctfont.bounding_box(); + let ascent = self.ctfont.ascent() as f64; + let descent = self.ctfont.descent() as f64; + let em_size = Au::from_frac_px(self.ctfont.pt_size() as f64); + let leading = self.ctfont.leading() as f64; + + let scale = px_to_pt(self.ctfont.pt_size() as f64) / (ascent + descent); + let line_gap = (ascent + descent + leading + 0.5).floor(); + + let metrics = FontMetrics { + underline_size: Au::from_pt(self.ctfont.underline_thickness() as f64), + // TODO(Issue #201): underline metrics are not reliable. Have to pull out of font table + // directly. + // + // see also: https://bugs.webkit.org/show_bug.cgi?id=16768 + // see also: https://bugreports.qt-project.org/browse/QTBUG-13364 + underline_offset: Au::from_pt(self.ctfont.underline_position() as f64), + strikeout_size: geometry::from_pt(0.0), // FIXME(Issue #942) + strikeout_offset: geometry::from_pt(0.0), // FIXME(Issue #942) + leading: Au::from_pt(leading), + x_height: Au::from_pt(self.ctfont.x_height() as f64), + em_size: em_size, + ascent: Au::from_pt(ascent * scale), + descent: Au::from_pt(descent * scale), + max_advance: Au::from_pt(bounding_rect.size.width as f64), + line_gap: Au::from_frac_px(line_gap), + }; + debug!("Font metrics (@{:f} pt): {:?}", self.ctfont.pt_size() as f64, metrics); + return metrics; + } + + fn get_table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> { + let result: Option<CFData> = self.ctfont.get_font_table(tag); + result.and_then(|data| { + Some(FontTable::wrap(data)) + }) + } +} + diff --git a/components/gfx/platform/macos/font_context.rs b/components/gfx/platform/macos/font_context.rs new file mode 100644 index 00000000000..94730641c3d --- /dev/null +++ b/components/gfx/platform/macos/font_context.rs @@ -0,0 +1,16 @@ +/* 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/. */ + +#[deriving(Clone)] +pub struct FontContextHandle { + ctx: () +} + +#[deriving(Clone)] +impl FontContextHandle { + // this is a placeholder until NSFontManager or whatever is bound in here. + pub fn new() -> FontContextHandle { + FontContextHandle { ctx: () } + } +} diff --git a/components/gfx/platform/macos/font_list.rs b/components/gfx/platform/macos/font_list.rs new file mode 100644 index 00000000000..4ec319ec6b2 --- /dev/null +++ b/components/gfx/platform/macos/font_list.rs @@ -0,0 +1,37 @@ +/* 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 core_foundation::base::TCFType; +use core_foundation::string::{CFString, CFStringRef}; +use core_text::font_descriptor::{CTFontDescriptor, CTFontDescriptorRef}; +use core_text; +use std::mem; + +pub fn get_available_families(callback: |String|) { + let family_names = core_text::font_collection::get_family_names(); + for strref in family_names.iter() { + let family_name_ref: CFStringRef = unsafe { mem::transmute(strref) }; + let family_name_cf: CFString = unsafe { TCFType::wrap_under_get_rule(family_name_ref) }; + let family_name = family_name_cf.to_string(); + callback(family_name); + } +} + +pub fn get_variations_for_family(family_name: &str, callback: |String|) { + debug!("Looking for faces of family: {:s}", family_name); + + let family_collection = + core_text::font_collection::create_for_family(family_name.as_slice()); + let family_descriptors = family_collection.get_descriptors(); + for descref in family_descriptors.iter() { + let descref: CTFontDescriptorRef = unsafe { mem::transmute(descref) }; + let desc: CTFontDescriptor = unsafe { TCFType::wrap_under_get_rule(descref) }; + let postscript_name = desc.font_name(); + callback(postscript_name); + } +} + +pub fn get_last_resort_font_families() -> Vec<String> { + vec!("Arial Unicode MS".to_string(), "Arial".to_string()) +} diff --git a/components/gfx/platform/macos/font_template.rs b/components/gfx/platform/macos/font_template.rs new file mode 100644 index 00000000000..8641d491523 --- /dev/null +++ b/components/gfx/platform/macos/font_template.rs @@ -0,0 +1,40 @@ +/* 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 core_graphics::data_provider::CGDataProvider; +use core_graphics::font::CGFont; +use core_text::font::CTFont; +use core_text; + +/// Platform specific font representation for mac. +/// The identifier is a PostScript font name. The +/// CTFont object is cached here for use by the +/// render functions that create CGFont references. +pub struct FontTemplateData { + pub ctfont: Option<CTFont>, + pub identifier: String, +} + +impl FontTemplateData { + pub fn new(identifier: &str, font_data: Option<Vec<u8>>) -> FontTemplateData { + let ctfont = match font_data { + Some(bytes) => { + let fontprov = CGDataProvider::from_buffer(bytes.as_slice()); + let cgfont_result = CGFont::from_data_provider(fontprov); + match cgfont_result { + Ok(cgfont) => Some(core_text::font::new_from_CGFont(&cgfont, 0.0)), + Err(_) => None + } + }, + None => { + Some(core_text::font::new_from_name(identifier.as_slice(), 0.0).unwrap()) + } + }; + + FontTemplateData { + ctfont: ctfont, + identifier: identifier.to_string(), + } + } +} diff --git a/components/gfx/platform/mod.rs b/components/gfx/platform/mod.rs new file mode 100644 index 00000000000..ded6f3888e8 --- /dev/null +++ b/components/gfx/platform/mod.rs @@ -0,0 +1,27 @@ +/* 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/. */ + +#[cfg(target_os="linux")] +#[cfg(target_os="android")] +pub use platform::freetype::{font, font_context, font_list, font_template}; + +#[cfg(target_os="macos")] +pub use platform::macos::{font, font_context, font_list, font_template}; + +#[cfg(target_os="linux")] +#[cfg(target_os="android")] +pub mod freetype { + pub mod font; + pub mod font_context; + pub mod font_list; + pub mod font_template; +} + +#[cfg(target_os="macos")] +pub mod macos { + pub mod font; + pub mod font_context; + pub mod font_list; + pub mod font_template; +} diff --git a/components/gfx/render_context.rs b/components/gfx/render_context.rs new file mode 100644 index 00000000000..68449cdff19 --- /dev/null +++ b/components/gfx/render_context.rs @@ -0,0 +1,419 @@ +/* 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 font_context::FontContext; +use style::computed_values::border_style; + +use azure::azure_hl::{B8G8R8A8, A8, Color, ColorPattern, DrawOptions, DrawSurfaceOptions, DrawTarget}; +use azure::azure_hl::{Linear, SourceOp, StrokeOptions}; +use azure::AZ_CAP_BUTT; +use azure::AzFloat; +use geom::point::Point2D; +use geom::rect::Rect; +use geom::size::Size2D; +use geom::side_offsets::SideOffsets2D; +use libc::types::common::c99::uint16_t; +use libc::size_t; +use png::{RGB8, RGBA8, K8, KA8}; +use servo_net::image::base::Image; +use servo_util::geometry::Au; +use servo_util::opts::Opts; +use sync::Arc; + +pub struct RenderContext<'a> { + pub draw_target: &'a DrawTarget, + pub font_ctx: &'a mut Box<FontContext>, + pub opts: &'a Opts, + /// The rectangle that this context encompasses in page coordinates. + pub page_rect: Rect<f32>, + /// The rectangle that this context encompasses in screen coordinates (pixels). + pub screen_rect: Rect<uint>, +} + +enum Direction { + Top, + Left, + Right, + Bottom +} + +enum DashSize { + DottedBorder = 1, + DashedBorder = 3 +} + +impl<'a> RenderContext<'a> { + pub fn get_draw_target(&self) -> &'a DrawTarget { + self.draw_target + } + + pub fn draw_solid_color(&self, bounds: &Rect<Au>, color: Color) { + self.draw_target.make_current(); + self.draw_target.fill_rect(&bounds.to_azure_rect(), &ColorPattern::new(color), None); + } + + pub fn draw_border(&self, + bounds: &Rect<Au>, + border: SideOffsets2D<Au>, + color: SideOffsets2D<Color>, + style: SideOffsets2D<border_style::T>) { + let border = border.to_float_px(); + self.draw_target.make_current(); + + self.draw_border_segment(Top, bounds, border, color, style); + self.draw_border_segment(Right, bounds, border, color, style); + self.draw_border_segment(Bottom, bounds, border, color, style); + self.draw_border_segment(Left, bounds, border, color, style); + } + + pub fn draw_line(&self, + bounds: &Rect<Au>, + color: Color, + style: border_style::T) { + self.draw_target.make_current(); + + self.draw_line_segment(bounds, color, style); + } + + pub fn draw_push_clip(&self, bounds: &Rect<Au>) { + let rect = bounds.to_azure_rect(); + let path_builder = self.draw_target.create_path_builder(); + + let left_top = Point2D(rect.origin.x, rect.origin.y); + let right_top = Point2D(rect.origin.x + rect.size.width, rect.origin.y); + let left_bottom = Point2D(rect.origin.x, rect.origin.y + rect.size.height); + let right_bottom = Point2D(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); + + path_builder.move_to(left_top); + path_builder.line_to(right_top); + path_builder.line_to(right_bottom); + path_builder.line_to(left_bottom); + + let path = path_builder.finish(); + self.draw_target.push_clip(&path); + } + + pub fn draw_pop_clip(&self) { + self.draw_target.pop_clip(); + } + + pub fn draw_image(&self, bounds: Rect<Au>, image: Arc<Box<Image>>) { + let size = Size2D(image.width as i32, image.height as i32); + let (pixel_width, pixels, source_format) = match image.pixels { + RGBA8(ref pixels) => (4, pixels.as_slice(), B8G8R8A8), + K8(ref pixels) => (1, pixels.as_slice(), A8), + RGB8(_) => fail!("RGB8 color type not supported"), + KA8(_) => fail!("KA8 color type not supported"), + }; + let stride = image.width * pixel_width; + + self.draw_target.make_current(); + let draw_target_ref = &self.draw_target; + let azure_surface = draw_target_ref.create_source_surface_from_data(pixels, + size, + stride as i32, + source_format); + let source_rect = Rect(Point2D(0u as AzFloat, 0u as AzFloat), + Size2D(image.width as AzFloat, image.height as AzFloat)); + let dest_rect = bounds.to_azure_rect(); + let draw_surface_options = DrawSurfaceOptions::new(Linear, true); + let draw_options = DrawOptions::new(1.0f64 as AzFloat, 0); + draw_target_ref.draw_surface(azure_surface, + dest_rect, + source_rect, + draw_surface_options, + draw_options); + } + + pub fn clear(&self) { + let pattern = ColorPattern::new(Color::new(0.0, 0.0, 0.0, 0.0)); + let rect = Rect(Point2D(self.page_rect.origin.x as AzFloat, + self.page_rect.origin.y as AzFloat), + Size2D(self.screen_rect.size.width as AzFloat, + self.screen_rect.size.height as AzFloat)); + let mut draw_options = DrawOptions::new(1.0, 0); + draw_options.set_composition_op(SourceOp); + self.draw_target.make_current(); + self.draw_target.fill_rect(&rect, &pattern, Some(&draw_options)); + } + + fn draw_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: SideOffsets2D<Color>, style: SideOffsets2D<border_style::T>) { + let (style_select, color_select) = match direction { + Top => (style.top, color.top), + Left => (style.left, color.left), + Right => (style.right, color.right), + Bottom => (style.bottom, color.bottom) + }; + + match style_select{ + border_style::none => { + } + border_style::hidden => { + } + //FIXME(sammykim): This doesn't work with dash_pattern and cap_style well. I referred firefox code. + border_style::dotted => { + self.draw_dashed_border_segment(direction, bounds, border, color_select, DottedBorder); + } + border_style::dashed => { + self.draw_dashed_border_segment(direction, bounds, border, color_select, DashedBorder); + } + border_style::solid => { + self.draw_solid_border_segment(direction,bounds,border,color_select); + } + border_style::double => { + self.draw_double_border_segment(direction, bounds, border, color_select); + } + border_style::groove | border_style::ridge => { + self.draw_groove_ridge_border_segment(direction, bounds, border, color_select, style_select); + } + border_style::inset | border_style::outset => { + self.draw_inset_outset_border_segment(direction, bounds, border, style_select, color_select); + } + } + } + + fn draw_line_segment(&self, bounds: &Rect<Au>, color: Color, style: border_style::T) { + let border = SideOffsets2D::new_all_same(bounds.size.width).to_float_px(); + + match style{ + border_style::none | border_style::hidden => {} + border_style::dotted => { + self.draw_dashed_border_segment(Right, bounds, border, color, DottedBorder); + } + border_style::dashed => { + self.draw_dashed_border_segment(Right, bounds, border, color, DashedBorder); + } + border_style::solid => { + self.draw_solid_border_segment(Right,bounds,border,color); + } + border_style::double => { + self.draw_double_border_segment(Right, bounds, border, color); + } + border_style::groove | border_style::ridge => { + self.draw_groove_ridge_border_segment(Right, bounds, border, color, style); + } + border_style::inset | border_style::outset => { + self.draw_inset_outset_border_segment(Right, bounds, border, style, color); + } + } + } + + fn draw_border_path(&self, + bounds: Rect<f32>, + direction: Direction, + border: SideOffsets2D<f32>, + color: Color) { + let left_top = bounds.origin; + let right_top = left_top + Point2D(bounds.size.width, 0.0); + let left_bottom = left_top + Point2D(0.0, bounds.size.height); + let right_bottom = left_top + Point2D(bounds.size.width, bounds.size.height); + let draw_opts = DrawOptions::new(1.0, 0); + let path_builder = self.draw_target.create_path_builder(); + match direction { + Top => { + path_builder.move_to(left_top); + path_builder.line_to(right_top); + path_builder.line_to(right_top + Point2D(-border.right, border.top)); + path_builder.line_to(left_top + Point2D(border.left, border.top)); + } + Left => { + path_builder.move_to(left_top); + path_builder.line_to(left_top + Point2D(border.left, border.top)); + path_builder.line_to(left_bottom + Point2D(border.left, -border.bottom)); + path_builder.line_to(left_bottom); + } + Right => { + path_builder.move_to(right_top); + path_builder.line_to(right_bottom); + path_builder.line_to(right_bottom + Point2D(-border.right, -border.bottom)); + path_builder.line_to(right_top + Point2D(-border.right, border.top)); + } + Bottom => { + path_builder.move_to(left_bottom); + path_builder.line_to(left_bottom + Point2D(border.left, -border.bottom)); + path_builder.line_to(right_bottom + Point2D(-border.right, -border.bottom)); + path_builder.line_to(right_bottom); + } + } + let path = path_builder.finish(); + self.draw_target.fill(&path, &ColorPattern::new(color), &draw_opts); + + } + + fn draw_dashed_border_segment(&self, + direction: Direction, + bounds: &Rect<Au>, + border: SideOffsets2D<f32>, + color: Color, + dash_size: DashSize) { + let rect = bounds.to_azure_rect(); + let draw_opts = DrawOptions::new(1u as AzFloat, 0 as uint16_t); + let mut stroke_opts = StrokeOptions::new(0u as AzFloat, 10u as AzFloat); + let mut dash: [AzFloat, ..2] = [0u as AzFloat, 0u as AzFloat]; + + stroke_opts.set_cap_style(AZ_CAP_BUTT as u8); + + let border_width = match direction { + Top => border.top, + Left => border.left, + Right => border.right, + Bottom => border.bottom + }; + + stroke_opts.line_width = border_width; + dash[0] = border_width * (dash_size as int) as AzFloat; + dash[1] = border_width * (dash_size as int) as AzFloat; + stroke_opts.mDashPattern = dash.as_mut_ptr(); + stroke_opts.mDashLength = dash.len() as size_t; + + let (start, end) = match direction { + Top => { + let y = rect.origin.y + border.top * 0.5; + let start = Point2D(rect.origin.x, y); + let end = Point2D(rect.origin.x + rect.size.width, y); + (start, end) + } + Left => { + let x = rect.origin.x + border.left * 0.5; + let start = Point2D(x, rect.origin.y + rect.size.height); + let end = Point2D(x, rect.origin.y + border.top); + (start, end) + } + Right => { + let x = rect.origin.x + rect.size.width - border.right * 0.5; + let start = Point2D(x, rect.origin.y); + let end = Point2D(x, rect.origin.y + rect.size.height); + (start, end) + } + Bottom => { + let y = rect.origin.y + rect.size.height - border.bottom * 0.5; + let start = Point2D(rect.origin.x + rect.size.width, y); + let end = Point2D(rect.origin.x + border.left, y); + (start, end) + } + }; + + self.draw_target.stroke_line(start, + end, + &ColorPattern::new(color), + &stroke_opts, + &draw_opts); + } + + fn draw_solid_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: Color) { + let rect = bounds.to_azure_rect(); + self.draw_border_path(rect, direction, border, color); + } + + fn get_scaled_bounds(&self, + bounds: &Rect<Au>, + border: SideOffsets2D<f32>, + shrink_factor: f32) -> Rect<f32> { + let rect = bounds.to_azure_rect(); + let scaled_border = SideOffsets2D::new(shrink_factor * border.top, + shrink_factor * border.right, + shrink_factor * border.bottom, + shrink_factor * border.left); + let left_top = Point2D(rect.origin.x, rect.origin.y); + let scaled_left_top = left_top + Point2D(scaled_border.left, + scaled_border.top); + return Rect(scaled_left_top, + Size2D(rect.size.width - 2.0 * scaled_border.right, rect.size.height - 2.0 * scaled_border.bottom)); + } + + fn scale_color(&self, color: Color, scale_factor: f32) -> Color { + return Color::new(color.r * scale_factor, color.g * scale_factor, color.b * scale_factor, color.a); + } + + fn draw_double_border_segment(&self, direction: Direction, bounds: &Rect<Au>, border: SideOffsets2D<f32>, color: Color) { + let scaled_border = SideOffsets2D::new((1.0/3.0) * border.top, + (1.0/3.0) * border.right, + (1.0/3.0) * border.bottom, + (1.0/3.0) * border.left); + let inner_scaled_bounds = self.get_scaled_bounds(bounds, border, 2.0/3.0); + // draw the outer portion of the double border. + self.draw_solid_border_segment(direction, bounds, scaled_border, color); + // draw the inner portion of the double border. + self.draw_border_path(inner_scaled_bounds, direction, scaled_border, color); + } + + fn draw_groove_ridge_border_segment(&self, + direction: Direction, + bounds: &Rect<Au>, + border: SideOffsets2D<f32>, + color: Color, + style: border_style::T) { + // original bounds as a Rect<f32>, with no scaling. + let original_bounds = self.get_scaled_bounds(bounds, border, 0.0); + // shrink the bounds by 1/2 of the border, leaving the innermost 1/2 of the border + let inner_scaled_bounds = self.get_scaled_bounds(bounds, border, 0.5); + let scaled_border = SideOffsets2D::new(0.5 * border.top, + 0.5 * border.right, + 0.5 * border.bottom, + 0.5 * border.left); + let is_groove = match style { + border_style::groove => true, + border_style::ridge => false, + _ => fail!("invalid border style") + }; + let darker_color = self.scale_color(color, if is_groove { 1.0/3.0 } else { 2.0/3.0 }); + let (outer_color, inner_color) = match (direction, is_groove) { + (Top, true) | (Left, true) | (Right, false) | (Bottom, false) => (darker_color, color), + (Top, false) | (Left, false) | (Right, true) | (Bottom, true) => (color, darker_color) + }; + // outer portion of the border + self.draw_border_path(original_bounds, direction, scaled_border, outer_color); + // inner portion of the border + self.draw_border_path(inner_scaled_bounds, direction, scaled_border, inner_color); + } + + fn draw_inset_outset_border_segment(&self, + direction: Direction, + bounds: &Rect<Au>, + border: SideOffsets2D<f32>, + style: border_style::T, + color: Color) { + let is_inset = match style { + border_style::inset => true, + border_style::outset => false, + _ => fail!("invalid border style") + }; + // original bounds as a Rect<f32> + let original_bounds = self.get_scaled_bounds(bounds, border, 0.0); + // select and scale the color appropriately. + let scaled_color = match direction { + Top => self.scale_color(color, if is_inset { 2.0/3.0 } else { 1.0 }), + Left => self.scale_color(color, if is_inset { 1.0/6.0 } else { 0.5 }), + Right | Bottom => self.scale_color(color, if is_inset { 1.0 } else { 2.0/3.0 }) + }; + self.draw_border_path(original_bounds, direction, border, scaled_color); + } + +} + +trait ToAzureRect { + fn to_azure_rect(&self) -> Rect<AzFloat>; +} + +impl ToAzureRect for Rect<Au> { + fn to_azure_rect(&self) -> Rect<AzFloat> { + Rect(Point2D(self.origin.x.to_nearest_px() as AzFloat, + self.origin.y.to_nearest_px() as AzFloat), + Size2D(self.size.width.to_nearest_px() as AzFloat, + self.size.height.to_nearest_px() as AzFloat)) + } +} + +trait ToSideOffsetsPx { + fn to_float_px(&self) -> SideOffsets2D<AzFloat>; +} + +impl ToSideOffsetsPx for SideOffsets2D<Au> { + fn to_float_px(&self) -> SideOffsets2D<AzFloat> { + SideOffsets2D::new(self.top.to_nearest_px() as AzFloat, + self.right.to_nearest_px() as AzFloat, + self.bottom.to_nearest_px() as AzFloat, + self.left.to_nearest_px() as AzFloat) + } +} diff --git a/components/gfx/render_task.rs b/components/gfx/render_task.rs new file mode 100644 index 00000000000..2d7b8b5e2d7 --- /dev/null +++ b/components/gfx/render_task.rs @@ -0,0 +1,443 @@ +/* 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/. */ + +//! The task that handles all rendering/painting. + +use buffer_map::BufferMap; +use display_list::optimizer::DisplayListOptimizer; +use display_list::DisplayList; +use font_context::FontContext; +use render_context::RenderContext; + +use azure::azure_hl::{B8G8R8A8, Color, DrawTarget, StolenGLResources}; +use azure::AzFloat; +use geom::matrix2d::Matrix2D; +use geom::rect::Rect; +use geom::size::Size2D; +use layers::platform::surface::{NativePaintingGraphicsContext, NativeSurface}; +use layers::platform::surface::{NativeSurfaceMethods}; +use layers::layers::{BufferRequest, LayerBuffer, LayerBufferSet}; +use layers; +use servo_msg::compositor_msg::{Epoch, IdleRenderState, LayerId}; +use servo_msg::compositor_msg::{LayerMetadata, RenderListener, RenderingRenderState, ScrollPolicy}; +use servo_msg::constellation_msg::{ConstellationChan, Failure, FailureMsg, PipelineId}; +use servo_msg::constellation_msg::{RendererReadyMsg}; +use servo_msg::platform::surface::NativeSurfaceAzureMethods; +use servo_util::geometry; +use servo_util::opts::Opts; +use servo_util::smallvec::{SmallVec, SmallVec1}; +use servo_util::task::spawn_named_with_send_on_failure; +use servo_util::time::{TimeProfilerChan, profile}; +use servo_util::time; +use std::comm::{Receiver, Sender, channel}; +use sync::Arc; +use font_cache_task::FontCacheTask; + +/// Information about a layer that layout sends to the painting task. +pub struct RenderLayer { + /// A per-pipeline ID describing this layer that should be stable across reflows. + pub id: LayerId, + /// The display list describing the contents of this layer. + pub display_list: Arc<DisplayList>, + /// The position of the layer in pixels. + pub position: Rect<uint>, + /// The color of the background in this layer. Used for unrendered content. + pub background_color: Color, + /// The scrolling policy of this layer. + pub scroll_policy: ScrollPolicy, +} + +pub struct RenderRequest { + pub buffer_requests: Vec<BufferRequest>, + pub scale: f32, + pub layer_id: LayerId, + pub epoch: Epoch, +} + +pub enum Msg { + RenderInitMsg(SmallVec1<RenderLayer>), + RenderMsg(Vec<RenderRequest>), + UnusedBufferMsg(Vec<Box<LayerBuffer>>), + PaintPermissionGranted, + PaintPermissionRevoked, + ExitMsg(Option<Sender<()>>), +} + +#[deriving(Clone)] +pub struct RenderChan(Sender<Msg>); + +impl RenderChan { + pub fn new() -> (Receiver<Msg>, RenderChan) { + let (chan, port) = channel(); + (port, RenderChan(chan)) + } + + pub fn send(&self, msg: Msg) { + let &RenderChan(ref chan) = self; + assert!(chan.send_opt(msg).is_ok(), "RenderChan.send: render port closed") + } + + pub fn send_opt(&self, msg: Msg) -> Result<(), Msg> { + let &RenderChan(ref chan) = self; + chan.send_opt(msg) + } +} + +/// If we're using GPU rendering, this provides the metadata needed to create a GL context that +/// is compatible with that of the main thread. +pub enum GraphicsContext { + CpuGraphicsContext, + GpuGraphicsContext, +} + +pub struct RenderTask<C> { + id: PipelineId, + port: Receiver<Msg>, + compositor: C, + constellation_chan: ConstellationChan, + font_ctx: Box<FontContext>, + opts: Opts, + + /// A channel to the time profiler. + time_profiler_chan: TimeProfilerChan, + + /// The graphics context to use. + graphics_context: GraphicsContext, + + /// The native graphics context. + native_graphics_context: Option<NativePaintingGraphicsContext>, + + /// The layers to be rendered. + render_layers: SmallVec1<RenderLayer>, + + /// Permission to send paint messages to the compositor + paint_permission: bool, + + /// A counter for epoch messages + epoch: Epoch, + + /// A data structure to store unused LayerBuffers + buffer_map: BufferMap, +} + +// If we implement this as a function, we get borrowck errors from borrowing +// the whole RenderTask struct. +macro_rules! native_graphics_context( + ($task:expr) => ( + $task.native_graphics_context.as_ref().expect("Need a graphics context to do rendering") + ) +) + +fn initialize_layers<C:RenderListener>( + compositor: &mut C, + pipeline_id: PipelineId, + epoch: Epoch, + render_layers: &[RenderLayer]) { + let metadata = render_layers.iter().map(|render_layer| { + LayerMetadata { + id: render_layer.id, + position: render_layer.position, + background_color: render_layer.background_color, + scroll_policy: render_layer.scroll_policy, + } + }).collect(); + compositor.initialize_layers_for_pipeline(pipeline_id, metadata, epoch); +} + +impl<C:RenderListener + Send> RenderTask<C> { + pub fn create(id: PipelineId, + port: Receiver<Msg>, + compositor: C, + constellation_chan: ConstellationChan, + font_cache_task: FontCacheTask, + failure_msg: Failure, + opts: Opts, + time_profiler_chan: TimeProfilerChan, + shutdown_chan: Sender<()>) { + + let ConstellationChan(c) = constellation_chan.clone(); + let fc = font_cache_task.clone(); + + spawn_named_with_send_on_failure("RenderTask", proc() { + { // Ensures RenderTask and graphics context are destroyed before shutdown msg + let native_graphics_context = compositor.get_graphics_metadata().map( + |md| NativePaintingGraphicsContext::from_metadata(&md)); + let cpu_painting = opts.cpu_painting; + + // FIXME: rust/#5967 + let mut render_task = RenderTask { + id: id, + port: port, + compositor: compositor, + constellation_chan: constellation_chan, + font_ctx: box FontContext::new(fc.clone()), + opts: opts, + time_profiler_chan: time_profiler_chan, + + graphics_context: if cpu_painting { + CpuGraphicsContext + } else { + GpuGraphicsContext + }, + + native_graphics_context: native_graphics_context, + + render_layers: SmallVec1::new(), + + paint_permission: false, + epoch: Epoch(0), + buffer_map: BufferMap::new(10000000), + }; + + render_task.start(); + + // Destroy all the buffers. + match render_task.native_graphics_context.as_ref() { + Some(ctx) => render_task.buffer_map.clear(ctx), + None => (), + } + } + + debug!("render_task: shutdown_chan send"); + shutdown_chan.send(()); + }, FailureMsg(failure_msg), c, true); + } + + fn start(&mut self) { + debug!("render_task: beginning rendering loop"); + + loop { + match self.port.recv() { + RenderInitMsg(render_layers) => { + self.epoch.next(); + self.render_layers = render_layers; + + if !self.paint_permission { + debug!("render_task: render ready msg"); + let ConstellationChan(ref mut c) = self.constellation_chan; + c.send(RendererReadyMsg(self.id)); + continue; + } + + initialize_layers(&mut self.compositor, + self.id, + self.epoch, + self.render_layers.as_slice()); + } + RenderMsg(requests) => { + if !self.paint_permission { + debug!("render_task: render ready msg"); + let ConstellationChan(ref mut c) = self.constellation_chan; + c.send(RendererReadyMsg(self.id)); + self.compositor.render_msg_discarded(); + continue; + } + + self.compositor.set_render_state(RenderingRenderState); + + let mut replies = Vec::new(); + for RenderRequest { buffer_requests, scale, layer_id, epoch } + in requests.move_iter() { + if self.epoch == epoch { + self.render(&mut replies, buffer_requests, scale, layer_id); + } else { + debug!("renderer epoch mismatch: {:?} != {:?}", self.epoch, epoch); + } + } + + self.compositor.set_render_state(IdleRenderState); + + debug!("render_task: returning surfaces"); + self.compositor.paint(self.id, self.epoch, replies); + } + UnusedBufferMsg(unused_buffers) => { + for buffer in unused_buffers.move_iter().rev() { + self.buffer_map.insert(native_graphics_context!(self), buffer); + } + } + PaintPermissionGranted => { + self.paint_permission = true; + + // Here we assume that the main layer—the layer responsible for the page size— + // is the first layer. This is a pretty fragile assumption. It will be fixed + // once we use the layers-based scrolling infrastructure for all scrolling. + if self.render_layers.len() > 1 { + self.epoch.next(); + initialize_layers(&mut self.compositor, + self.id, + self.epoch, + self.render_layers.as_slice()); + } + } + PaintPermissionRevoked => { + self.paint_permission = false; + } + ExitMsg(response_ch) => { + debug!("render_task: exitmsg response send"); + response_ch.map(|ch| ch.send(())); + break; + } + } + } + } + + /// Renders one layer and sends the tiles back to the layer. + fn render(&mut self, + replies: &mut Vec<(LayerId, Box<LayerBufferSet>)>, + tiles: Vec<BufferRequest>, + scale: f32, + layer_id: LayerId) { + time::profile(time::RenderingCategory, self.time_profiler_chan.clone(), || { + // FIXME: Try not to create a new array here. + let mut new_buffers = vec!(); + + // Find the appropriate render layer. + let render_layer = match self.render_layers.iter().find(|layer| layer.id == layer_id) { + Some(render_layer) => render_layer, + None => return, + }; + + // Divide up the layer into tiles. + for tile in tiles.iter() { + // Optimize the display list for this tile. + let page_rect_au = geometry::f32_rect_to_au_rect(tile.page_rect); + let optimizer = DisplayListOptimizer::new(render_layer.display_list.clone(), + page_rect_au); + let display_list = optimizer.optimize(); + + let width = tile.screen_rect.size.width; + let height = tile.screen_rect.size.height; + + let size = Size2D(width as i32, height as i32); + let draw_target = match self.graphics_context { + CpuGraphicsContext => { + DrawTarget::new(self.opts.render_backend, size, B8G8R8A8) + } + GpuGraphicsContext => { + // FIXME(pcwalton): Cache the components of draw targets + // (texture color buffer, renderbuffers) instead of recreating them. + let draw_target = + DrawTarget::new_with_fbo(self.opts.render_backend, + native_graphics_context!(self), + size, + B8G8R8A8); + draw_target.make_current(); + draw_target + } + }; + + { + // Build the render context. + let mut ctx = RenderContext { + draw_target: &draw_target, + font_ctx: &mut self.font_ctx, + opts: &self.opts, + page_rect: tile.page_rect, + screen_rect: tile.screen_rect, + }; + + // Apply the translation to render the tile we want. + let matrix: Matrix2D<AzFloat> = Matrix2D::identity(); + let matrix = matrix.scale(scale as AzFloat, scale as AzFloat); + let matrix = matrix.translate(-(tile.page_rect.origin.x) as AzFloat, + -(tile.page_rect.origin.y) as AzFloat); + let matrix = matrix.translate(-(render_layer.position.origin.x as AzFloat), + -(render_layer.position.origin.y as AzFloat)); + + ctx.draw_target.set_transform(&matrix); + + // Clear the buffer. + ctx.clear(); + + // Draw the display list. + profile(time::RenderingDrawingCategory, self.time_profiler_chan.clone(), || { + display_list.draw_into_context(&mut ctx, &matrix); + ctx.draw_target.flush(); + }); + } + + // Extract the texture from the draw target and place it into its slot in the + // buffer. If using CPU rendering, upload it first. + // + // FIXME(pcwalton): We should supply the texture and native surface *to* the + // draw target in GPU rendering mode, so that it doesn't have to recreate it. + let buffer = match self.graphics_context { + CpuGraphicsContext => { + let mut buffer = match self.buffer_map.find(tile.screen_rect.size) { + Some(buffer) => { + let mut buffer = buffer; + buffer.rect = tile.page_rect; + buffer.screen_pos = tile.screen_rect; + buffer.resolution = scale; + buffer.native_surface.mark_wont_leak(); + buffer.painted_with_cpu = true; + buffer.content_age = tile.content_age; + buffer + } + None => { + // Create an empty native surface. We mark it as not leaking + // in case it dies in transit to the compositor task. + let mut native_surface: NativeSurface = + layers::platform::surface::NativeSurfaceMethods::new( + native_graphics_context!(self), + Size2D(width as i32, height as i32), + width as i32 * 4); + native_surface.mark_wont_leak(); + + box LayerBuffer { + native_surface: native_surface, + rect: tile.page_rect, + screen_pos: tile.screen_rect, + resolution: scale, + stride: (width * 4) as uint, + painted_with_cpu: true, + content_age: tile.content_age, + } + } + }; + + draw_target.snapshot().get_data_surface().with_data(|data| { + buffer.native_surface.upload(native_graphics_context!(self), data); + debug!("RENDERER uploading to native surface {:d}", + buffer.native_surface.get_id() as int); + }); + + buffer + } + GpuGraphicsContext => { + draw_target.make_current(); + let StolenGLResources { + surface: native_surface + } = draw_target.steal_gl_resources().unwrap(); + + // We mark the native surface as not leaking in case the surfaces + // die on their way to the compositor task. + let mut native_surface: NativeSurface = + NativeSurfaceAzureMethods::from_azure_surface(native_surface); + native_surface.mark_wont_leak(); + + box LayerBuffer { + native_surface: native_surface, + rect: tile.page_rect, + screen_pos: tile.screen_rect, + resolution: scale, + stride: (width * 4) as uint, + painted_with_cpu: false, + content_age: tile.content_age, + } + } + }; + + new_buffers.push(buffer); + } + + let layer_buffer_set = box LayerBufferSet { + buffers: new_buffers, + }; + + replies.push((render_layer.id, layer_buffer_set)); + }) + } +} + diff --git a/components/gfx/text/glyph.rs b/components/gfx/text/glyph.rs new file mode 100644 index 00000000000..2ea2d7c5d2e --- /dev/null +++ b/components/gfx/text/glyph.rs @@ -0,0 +1,752 @@ +/* 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 servo_util::vec::*; +use servo_util::range; +use servo_util::range::{Range, RangeIndex, IntRangeIndex, EachIndex}; +use servo_util::geometry::Au; + +use std::cmp::{PartialOrd, PartialEq}; +use std::num::{NumCast, Zero}; +use std::mem; +use std::u16; +use std::vec::Vec; +use geom::point::Point2D; + +/// GlyphEntry is a port of Gecko's CompressedGlyph scheme for storing glyph data compactly. +/// +/// In the common case (reasonable glyph advances, no offsets from the font em-box, and one glyph +/// per character), we pack glyph advance, glyph id, and some flags into a single u32. +/// +/// In the uncommon case (multiple glyphs per unicode character, large glyph index/advance, or +/// glyph offsets), we pack the glyph count into GlyphEntry, and store the other glyph information +/// in DetailedGlyphStore. +#[deriving(Clone)] +struct GlyphEntry { + value: u32, +} + +impl GlyphEntry { + fn new(value: u32) -> GlyphEntry { + GlyphEntry { + value: value, + } + } + + fn initial() -> GlyphEntry { + GlyphEntry::new(0) + } + + // Creates a GlyphEntry for the common case + fn simple(id: GlyphId, advance: Au) -> GlyphEntry { + assert!(is_simple_glyph_id(id)); + assert!(is_simple_advance(advance)); + + let id_mask = id as u32; + let Au(advance) = advance; + let advance_mask = (advance as u32) << GLYPH_ADVANCE_SHIFT as uint; + + GlyphEntry::new(id_mask | advance_mask | FLAG_IS_SIMPLE_GLYPH) + } + + // Create a GlyphEntry for uncommon case; should be accompanied by + // initialization of the actual DetailedGlyph data in DetailedGlyphStore + fn complex(starts_cluster: bool, starts_ligature: bool, glyph_count: int) -> GlyphEntry { + assert!(glyph_count <= u16::MAX as int); + + debug!("creating complex glyph entry: starts_cluster={}, starts_ligature={}, \ + glyph_count={}", + starts_cluster, + starts_ligature, + glyph_count); + + let mut val = FLAG_NOT_MISSING; + + if !starts_cluster { + val |= FLAG_NOT_CLUSTER_START; + } + if !starts_ligature { + val |= FLAG_NOT_LIGATURE_GROUP_START; + } + val |= (glyph_count as u32) << GLYPH_COUNT_SHIFT as uint; + + GlyphEntry::new(val) + } + + /// Create a GlyphEntry for the case where glyphs couldn't be found for the specified + /// character. + fn missing(glyph_count: int) -> GlyphEntry { + assert!(glyph_count <= u16::MAX as int); + + GlyphEntry::new((glyph_count as u32) << GLYPH_COUNT_SHIFT as uint) + } +} + +/// The id of a particular glyph within a font +pub type GlyphId = u32; + +// TODO: unify with bit flags? +#[deriving(PartialEq)] +pub enum BreakType { + BreakTypeNone, + BreakTypeNormal, + BreakTypeHyphen, +} + +static BREAK_TYPE_NONE: u8 = 0x0; +static BREAK_TYPE_NORMAL: u8 = 0x1; +static BREAK_TYPE_HYPHEN: u8 = 0x2; + +fn break_flag_to_enum(flag: u8) -> BreakType { + if (flag & BREAK_TYPE_NORMAL) != 0 { + BreakTypeNormal + } else if (flag & BREAK_TYPE_HYPHEN) != 0 { + BreakTypeHyphen + } else { + BreakTypeNone + } +} + +fn break_enum_to_flag(e: BreakType) -> u8 { + match e { + BreakTypeNone => BREAK_TYPE_NONE, + BreakTypeNormal => BREAK_TYPE_NORMAL, + BreakTypeHyphen => BREAK_TYPE_HYPHEN, + } +} + +// TODO: make this more type-safe. + +static FLAG_CHAR_IS_SPACE: u32 = 0x10000000; +// These two bits store some BREAK_TYPE_* flags +static FLAG_CAN_BREAK_MASK: u32 = 0x60000000; +static FLAG_CAN_BREAK_SHIFT: u32 = 29; +static FLAG_IS_SIMPLE_GLYPH: u32 = 0x80000000; + +// glyph advance; in Au's. +static GLYPH_ADVANCE_MASK: u32 = 0x0FFF0000; +static GLYPH_ADVANCE_SHIFT: u32 = 16; +static GLYPH_ID_MASK: u32 = 0x0000FFFF; + +// Non-simple glyphs (more than one glyph per char; missing glyph, +// newline, tab, large advance, or nonzero x/y offsets) may have one +// or more detailed glyphs associated with them. They are stored in a +// side array so that there is a 1:1 mapping of GlyphEntry to +// unicode char. + +// The number of detailed glyphs for this char. If the char couldn't +// be mapped to a glyph (!FLAG_NOT_MISSING), then this actually holds +// the UTF8 code point instead. +static GLYPH_COUNT_MASK: u32 = 0x00FFFF00; +static GLYPH_COUNT_SHIFT: u32 = 8; +// N.B. following Gecko, these are all inverted so that a lot of +// missing chars can be memset with zeros in one fell swoop. +static FLAG_NOT_MISSING: u32 = 0x00000001; +static FLAG_NOT_CLUSTER_START: u32 = 0x00000002; +static FLAG_NOT_LIGATURE_GROUP_START: u32 = 0x00000004; + +static FLAG_CHAR_IS_TAB: u32 = 0x00000008; +static FLAG_CHAR_IS_NEWLINE: u32 = 0x00000010; +//static FLAG_CHAR_IS_LOW_SURROGATE: u32 = 0x00000020; +//static CHAR_IDENTITY_FLAGS_MASK: u32 = 0x00000038; + +fn is_simple_glyph_id(id: GlyphId) -> bool { + ((id as u32) & GLYPH_ID_MASK) == id +} + +fn is_simple_advance(advance: Au) -> bool { + let unsignedAu = advance.to_u32().unwrap(); + (unsignedAu & (GLYPH_ADVANCE_MASK >> GLYPH_ADVANCE_SHIFT as uint)) == unsignedAu +} + +type DetailedGlyphCount = u16; + +// Getters and setters for GlyphEntry. Setter methods are functional, +// because GlyphEntry is immutable and only a u32 in size. +impl GlyphEntry { + // getter methods + #[inline(always)] + fn advance(&self) -> Au { + NumCast::from((self.value & GLYPH_ADVANCE_MASK) >> GLYPH_ADVANCE_SHIFT as uint).unwrap() + } + + fn id(&self) -> GlyphId { + self.value & GLYPH_ID_MASK + } + + fn is_ligature_start(&self) -> bool { + self.has_flag(!FLAG_NOT_LIGATURE_GROUP_START) + } + + fn is_cluster_start(&self) -> bool { + self.has_flag(!FLAG_NOT_CLUSTER_START) + } + + // True if original char was normal (U+0020) space. Other chars may + // map to space glyph, but this does not account for them. + fn char_is_space(&self) -> bool { + self.has_flag(FLAG_CHAR_IS_SPACE) + } + + fn char_is_tab(&self) -> bool { + !self.is_simple() && self.has_flag(FLAG_CHAR_IS_TAB) + } + + fn char_is_newline(&self) -> bool { + !self.is_simple() && self.has_flag(FLAG_CHAR_IS_NEWLINE) + } + + fn can_break_before(&self) -> BreakType { + let flag = ((self.value & FLAG_CAN_BREAK_MASK) >> FLAG_CAN_BREAK_SHIFT as uint) as u8; + break_flag_to_enum(flag) + } + + // setter methods + #[inline(always)] + fn set_char_is_space(&self) -> GlyphEntry { + GlyphEntry::new(self.value | FLAG_CHAR_IS_SPACE) + } + + #[inline(always)] + fn set_char_is_tab(&self) -> GlyphEntry { + assert!(!self.is_simple()); + GlyphEntry::new(self.value | FLAG_CHAR_IS_TAB) + } + + #[inline(always)] + fn set_char_is_newline(&self) -> GlyphEntry { + assert!(!self.is_simple()); + GlyphEntry::new(self.value | FLAG_CHAR_IS_NEWLINE) + } + + #[inline(always)] + fn set_can_break_before(&self, e: BreakType) -> GlyphEntry { + let flag = (break_enum_to_flag(e) as u32) << FLAG_CAN_BREAK_SHIFT as uint; + GlyphEntry::new(self.value | flag) + } + + // helper methods + + fn glyph_count(&self) -> u16 { + assert!(!self.is_simple()); + ((self.value & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT as uint) as u16 + } + + #[inline(always)] + fn is_simple(&self) -> bool { + self.has_flag(FLAG_IS_SIMPLE_GLYPH) + } + + #[inline(always)] + fn has_flag(&self, flag: u32) -> bool { + (self.value & flag) != 0 + } + + #[inline(always)] + fn adapt_character_flags_of_entry(&self, other: GlyphEntry) -> GlyphEntry { + GlyphEntry { value: self.value | other.value } + } +} + +// Stores data for a detailed glyph, in the case that several glyphs +// correspond to one character, or the glyph's data couldn't be packed. +#[deriving(Clone)] +struct DetailedGlyph { + id: GlyphId, + // glyph's advance, in the text's direction (RTL or RTL) + advance: Au, + // glyph's offset from the font's em-box (from top-left) + offset: Point2D<Au>, +} + +impl DetailedGlyph { + fn new(id: GlyphId, advance: Au, offset: Point2D<Au>) -> DetailedGlyph { + DetailedGlyph { + id: id, + advance: advance, + offset: offset, + } + } +} + +#[deriving(PartialEq, Clone, Eq)] +struct DetailedGlyphRecord { + // source string offset/GlyphEntry offset in the TextRun + entry_offset: CharIndex, + // offset into the detailed glyphs buffer + detail_offset: int, +} + +impl PartialOrd for DetailedGlyphRecord { + fn partial_cmp(&self, other: &DetailedGlyphRecord) -> Option<Ordering> { + self.entry_offset.partial_cmp(&other.entry_offset) + } +} + +impl Ord for DetailedGlyphRecord { + fn cmp(&self, other: &DetailedGlyphRecord) -> Ordering { + self.entry_offset.cmp(&other.entry_offset) + } +} + +// Manages the lookup table for detailed glyphs. Sorting is deferred +// until a lookup is actually performed; this matches the expected +// usage pattern of setting/appending all the detailed glyphs, and +// then querying without setting. +struct DetailedGlyphStore { + // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector + // optimization. + detail_buffer: Vec<DetailedGlyph>, + // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector + // optimization. + detail_lookup: Vec<DetailedGlyphRecord>, + lookup_is_sorted: bool, +} + +impl<'a> DetailedGlyphStore { + fn new() -> DetailedGlyphStore { + DetailedGlyphStore { + detail_buffer: vec!(), // TODO: default size? + detail_lookup: vec!(), + lookup_is_sorted: false, + } + } + + fn add_detailed_glyphs_for_entry(&mut self, entry_offset: CharIndex, glyphs: &[DetailedGlyph]) { + let entry = DetailedGlyphRecord { + entry_offset: entry_offset, + detail_offset: self.detail_buffer.len() as int, + }; + + debug!("Adding entry[off={}] for detailed glyphs: {:?}", entry_offset, glyphs); + + /* TODO: don't actually assert this until asserts are compiled + in/out based on severity, debug/release, etc. This assertion + would wreck the complexity of the lookup. + + See Rust Issue #3647, #2228, #3627 for related information. + + do self.detail_lookup.borrow |arr| { + assert !arr.contains(entry) + } + */ + + self.detail_lookup.push(entry); + self.detail_buffer.push_all(glyphs); + self.lookup_is_sorted = false; + } + + fn get_detailed_glyphs_for_entry(&'a self, entry_offset: CharIndex, count: u16) + -> &'a [DetailedGlyph] { + debug!("Requesting detailed glyphs[n={}] for entry[off={}]", count, entry_offset); + + // FIXME: Is this right? --pcwalton + // TODO: should fix this somewhere else + if count == 0 { + return self.detail_buffer.slice(0, 0); + } + + assert!((count as uint) <= self.detail_buffer.len()); + assert!(self.lookup_is_sorted); + + let key = DetailedGlyphRecord { + entry_offset: entry_offset, + detail_offset: 0, // unused + }; + + let i = self.detail_lookup.as_slice().binary_search_index(&key) + .expect("Invalid index not found in detailed glyph lookup table!"); + + assert!(i + (count as uint) <= self.detail_buffer.len()); + // return a slice into the buffer + self.detail_buffer.slice(i, i + count as uint) + } + + fn get_detailed_glyph_with_index(&'a self, + entry_offset: CharIndex, + detail_offset: u16) + -> &'a DetailedGlyph { + assert!((detail_offset as uint) <= self.detail_buffer.len()); + assert!(self.lookup_is_sorted); + + let key = DetailedGlyphRecord { + entry_offset: entry_offset, + detail_offset: 0, // unused + }; + + let i = self.detail_lookup.as_slice().binary_search_index(&key) + .expect("Invalid index not found in detailed glyph lookup table!"); + + assert!(i + (detail_offset as uint) < self.detail_buffer.len()); + &self.detail_buffer[i + (detail_offset as uint)] + } + + fn ensure_sorted(&mut self) { + if self.lookup_is_sorted { + return; + } + + // Sorting a unique vector is surprisingly hard. The follwing + // code is a good argument for using DVecs, but they require + // immutable locations thus don't play well with freezing. + + // Thar be dragons here. You have been warned. (Tips accepted.) + let mut unsorted_records: Vec<DetailedGlyphRecord> = vec!(); + mem::swap(&mut self.detail_lookup, &mut unsorted_records); + let mut mut_records : Vec<DetailedGlyphRecord> = unsorted_records; + mut_records.sort_by(|a, b| { + if a < b { + Less + } else { + Greater + } + }); + let mut sorted_records = mut_records; + mem::swap(&mut self.detail_lookup, &mut sorted_records); + + self.lookup_is_sorted = true; + } +} + +// This struct is used by GlyphStore clients to provide new glyph data. +// It should be allocated on the stack and passed by reference to GlyphStore. +pub struct GlyphData { + id: GlyphId, + advance: Au, + offset: Point2D<Au>, + is_missing: bool, + cluster_start: bool, + ligature_start: bool, +} + +impl GlyphData { + pub fn new(id: GlyphId, + advance: Au, + offset: Option<Point2D<Au>>, + is_missing: bool, + cluster_start: bool, + ligature_start: bool) + -> GlyphData { + GlyphData { + id: id, + advance: advance, + offset: offset.unwrap_or(Zero::zero()), + is_missing: is_missing, + cluster_start: cluster_start, + ligature_start: ligature_start, + } + } +} + +// This enum is a proxy that's provided to GlyphStore clients when iterating +// through glyphs (either for a particular TextRun offset, or all glyphs). +// Rather than eagerly assembling and copying glyph data, it only retrieves +// values as they are needed from the GlyphStore, using provided offsets. +pub enum GlyphInfo<'a> { + SimpleGlyphInfo(&'a GlyphStore, CharIndex), + DetailGlyphInfo(&'a GlyphStore, CharIndex, u16), +} + +impl<'a> GlyphInfo<'a> { + pub fn id(self) -> GlyphId { + match self { + SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i.to_uint()].id(), + DetailGlyphInfo(store, entry_i, detail_j) => { + store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).id + } + } + } + + #[inline(always)] + // FIXME: Resolution conflicts with IteratorUtil trait so adding trailing _ + pub fn advance(self) -> Au { + match self { + SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i.to_uint()].advance(), + DetailGlyphInfo(store, entry_i, detail_j) => { + store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).advance + } + } + } + + pub fn offset(self) -> Option<Point2D<Au>> { + match self { + SimpleGlyphInfo(_, _) => None, + DetailGlyphInfo(store, entry_i, detail_j) => { + Some(store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).offset) + } + } + } +} + +/// Stores the glyph data belonging to a text run. +/// +/// Simple glyphs are stored inline in the `entry_buffer`, detailed glyphs are +/// stored as pointers into the `detail_store`. +/// +/// ~~~ +/// +- GlyphStore --------------------------------+ +/// | +---+---+---+---+---+---+---+ | +/// | entry_buffer: | | s | | s | | s | s | | d = detailed +/// | +-|-+---+-|-+---+-|-+---+---+ | s = simple +/// | | | | | +/// | | +---+-------+ | +/// | | | | +/// | +-V-+-V-+ | +/// | detail_store: | d | d | | +/// | +---+---+ | +/// +---------------------------------------------+ +/// ~~~ +pub struct GlyphStore { + // TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector + // optimization. + /// A buffer of glyphs within the text run, in the order in which they + /// appear in the input text + entry_buffer: Vec<GlyphEntry>, + /// A store of the detailed glyph data. Detailed glyphs contained in the + /// `entry_buffer` point to locations in this data structure. + detail_store: DetailedGlyphStore, + + is_whitespace: bool, +} + +int_range_index! { + #[deriving(Encodable)] + #[doc = "An index that refers to a character in a text run. This could \ + point to the middle of a glyph."] + struct CharIndex(int) +} + +impl<'a> GlyphStore { + // Initializes the glyph store, but doesn't actually shape anything. + // Use the set_glyph, set_glyphs() methods to store glyph data. + pub fn new(length: int, is_whitespace: bool) -> GlyphStore { + assert!(length > 0); + + GlyphStore { + entry_buffer: Vec::from_elem(length as uint, GlyphEntry::initial()), + detail_store: DetailedGlyphStore::new(), + is_whitespace: is_whitespace, + } + } + + pub fn char_len(&self) -> CharIndex { + CharIndex(self.entry_buffer.len() as int) + } + + pub fn is_whitespace(&self) -> bool { + self.is_whitespace + } + + pub fn finalize_changes(&mut self) { + self.detail_store.ensure_sorted(); + } + + pub fn add_glyph_for_char_index(&mut self, i: CharIndex, data: &GlyphData) { + fn glyph_is_compressible(data: &GlyphData) -> bool { + is_simple_glyph_id(data.id) + && is_simple_advance(data.advance) + && data.offset.is_zero() + && data.cluster_start // others are stored in detail buffer + } + + assert!(data.ligature_start); // can't compress ligature continuation glyphs. + assert!(i < self.char_len()); + + let entry = match (data.is_missing, glyph_is_compressible(data)) { + (true, _) => GlyphEntry::missing(1), + (false, true) => GlyphEntry::simple(data.id, data.advance), + (false, false) => { + let glyph = [DetailedGlyph::new(data.id, data.advance, data.offset)]; + self.detail_store.add_detailed_glyphs_for_entry(i, glyph); + GlyphEntry::complex(data.cluster_start, data.ligature_start, 1) + } + }.adapt_character_flags_of_entry(self.entry_buffer[i.to_uint()]); + + *self.entry_buffer.get_mut(i.to_uint()) = entry; + } + + pub fn add_glyphs_for_char_index(&mut self, i: CharIndex, data_for_glyphs: &[GlyphData]) { + assert!(i < self.char_len()); + assert!(data_for_glyphs.len() > 0); + + let glyph_count = data_for_glyphs.len() as int; + + let first_glyph_data = data_for_glyphs[0]; + let entry = match first_glyph_data.is_missing { + true => GlyphEntry::missing(glyph_count), + false => { + let glyphs_vec = Vec::from_fn(glyph_count as uint, |i| { + DetailedGlyph::new(data_for_glyphs[i].id, + data_for_glyphs[i].advance, + data_for_glyphs[i].offset) + }); + + self.detail_store.add_detailed_glyphs_for_entry(i, glyphs_vec.as_slice()); + GlyphEntry::complex(first_glyph_data.cluster_start, + first_glyph_data.ligature_start, + glyph_count) + } + }.adapt_character_flags_of_entry(self.entry_buffer[i.to_uint()]); + + debug!("Adding multiple glyphs[idx={}, count={}]: {:?}", i, glyph_count, entry); + + *self.entry_buffer.get_mut(i.to_uint()) = entry; + } + + // used when a character index has no associated glyph---for example, a ligature continuation. + pub fn add_nonglyph_for_char_index(&mut self, i: CharIndex, cluster_start: bool, ligature_start: bool) { + assert!(i < self.char_len()); + + let entry = GlyphEntry::complex(cluster_start, ligature_start, 0); + debug!("adding spacer for chracter without associated glyph[idx={}]", i); + + *self.entry_buffer.get_mut(i.to_uint()) = entry; + } + + pub fn iter_glyphs_for_char_index(&'a self, i: CharIndex) -> GlyphIterator<'a> { + self.iter_glyphs_for_char_range(&Range::new(i, CharIndex(1))) + } + + #[inline] + pub fn iter_glyphs_for_char_range(&'a self, rang: &Range<CharIndex>) -> GlyphIterator<'a> { + if rang.begin() >= self.char_len() { + fail!("iter_glyphs_for_range: range.begin beyond length!"); + } + if rang.end() > self.char_len() { + fail!("iter_glyphs_for_range: range.end beyond length!"); + } + + GlyphIterator { + store: self, + char_index: rang.begin(), + char_range: rang.each_index(), + glyph_range: None, + } + } + + #[inline] + pub fn advance_for_char_range(&self, rang: &Range<CharIndex>) -> Au { + self.iter_glyphs_for_char_range(rang) + .fold(Au(0), |advance, (_, glyph)| advance + glyph.advance()) + } + + // getter methods + pub fn char_is_space(&self, i: CharIndex) -> bool { + assert!(i < self.char_len()); + self.entry_buffer[i.to_uint()].char_is_space() + } + + pub fn char_is_tab(&self, i: CharIndex) -> bool { + assert!(i < self.char_len()); + self.entry_buffer[i.to_uint()].char_is_tab() + } + + pub fn char_is_newline(&self, i: CharIndex) -> bool { + assert!(i < self.char_len()); + self.entry_buffer[i.to_uint()].char_is_newline() + } + + pub fn is_ligature_start(&self, i: CharIndex) -> bool { + assert!(i < self.char_len()); + self.entry_buffer[i.to_uint()].is_ligature_start() + } + + pub fn is_cluster_start(&self, i: CharIndex) -> bool { + assert!(i < self.char_len()); + self.entry_buffer[i.to_uint()].is_cluster_start() + } + + pub fn can_break_before(&self, i: CharIndex) -> BreakType { + assert!(i < self.char_len()); + self.entry_buffer[i.to_uint()].can_break_before() + } + + // setter methods + pub fn set_char_is_space(&mut self, i: CharIndex) { + assert!(i < self.char_len()); + let entry = self.entry_buffer[i.to_uint()]; + *self.entry_buffer.get_mut(i.to_uint()) = entry.set_char_is_space(); + } + + pub fn set_char_is_tab(&mut self, i: CharIndex) { + assert!(i < self.char_len()); + let entry = self.entry_buffer[i.to_uint()]; + *self.entry_buffer.get_mut(i.to_uint()) = entry.set_char_is_tab(); + } + + pub fn set_char_is_newline(&mut self, i: CharIndex) { + assert!(i < self.char_len()); + let entry = self.entry_buffer[i.to_uint()]; + *self.entry_buffer.get_mut(i.to_uint()) = entry.set_char_is_newline(); + } + + pub fn set_can_break_before(&mut self, i: CharIndex, t: BreakType) { + assert!(i < self.char_len()); + let entry = self.entry_buffer[i.to_uint()]; + *self.entry_buffer.get_mut(i.to_uint()) = entry.set_can_break_before(t); + } +} + +/// An iterator over the glyphs in a character range in a `GlyphStore`. +pub struct GlyphIterator<'a> { + store: &'a GlyphStore, + char_index: CharIndex, + char_range: EachIndex<int, CharIndex>, + glyph_range: Option<EachIndex<int, CharIndex>>, +} + +impl<'a> GlyphIterator<'a> { + // Slow path when there is a glyph range. + #[inline(never)] + fn next_glyph_range(&mut self) -> Option<(CharIndex, GlyphInfo<'a>)> { + match self.glyph_range.get_mut_ref().next() { + Some(j) => Some((self.char_index, + DetailGlyphInfo(self.store, self.char_index, j.get() as u16 /* ??? */))), + None => { + // No more glyphs for current character. Try to get another. + self.glyph_range = None; + self.next() + } + } + } + + // Slow path when there is a complex glyph. + #[inline(never)] + fn next_complex_glyph(&mut self, entry: &GlyphEntry, i: CharIndex) + -> Option<(CharIndex, GlyphInfo<'a>)> { + let glyphs = self.store.detail_store.get_detailed_glyphs_for_entry(i, entry.glyph_count()); + self.glyph_range = Some(range::each_index(CharIndex(0), CharIndex(glyphs.len() as int))); + self.next() + } +} + +impl<'a> Iterator<(CharIndex, GlyphInfo<'a>)> for GlyphIterator<'a> { + // I tried to start with something simpler and apply FlatMap, but the + // inability to store free variables in the FlatMap struct was problematic. + // + // This function consists of the fast path and is designed to be inlined into its caller. The + // slow paths, which should not be inlined, are `next_glyph_range()` and + // `next_complex_glyph()`. + #[inline(always)] + fn next(&mut self) -> Option<(CharIndex, GlyphInfo<'a>)> { + // Would use 'match' here but it borrows contents in a way that + // interferes with mutation. + if self.glyph_range.is_some() { + self.next_glyph_range() + } else { + // No glyph range. Look at next character. + self.char_range.next().and_then(|i| { + self.char_index = i; + assert!(i < self.store.char_len()); + let entry = self.store.entry_buffer[i.to_uint()]; + if entry.is_simple() { + Some((self.char_index, SimpleGlyphInfo(self.store, i))) + } else { + // Fall back to the slow path. + self.next_complex_glyph(&entry, i) + } + }) + } + } +} diff --git a/components/gfx/text/mod.rs b/components/gfx/text/mod.rs new file mode 100644 index 00000000000..f705347c441 --- /dev/null +++ b/components/gfx/text/mod.rs @@ -0,0 +1,18 @@ +/* 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/. */ + +/* This file exists just to make it easier to import things inside of + ./text/ without specifying the file they came out of imports. + +Note that you still must define each of the files as a module in +servo.rc. This is not ideal and may be changed in the future. */ + +pub use text::shaping::Shaper; +pub use text::text_run::TextRun; + +pub mod glyph; +#[path="shaping/mod.rs"] pub mod shaping; +pub mod text_run; +pub mod util; + diff --git a/components/gfx/text/shaping/harfbuzz.rs b/components/gfx/text/shaping/harfbuzz.rs new file mode 100644 index 00000000000..789126e767d --- /dev/null +++ b/components/gfx/text/shaping/harfbuzz.rs @@ -0,0 +1,541 @@ +/* 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/. */ + +extern crate harfbuzz; + +use font::{Font, FontHandleMethods, FontTableMethods, FontTableTag}; +use platform::font::FontTable; +use text::glyph::{CharIndex, GlyphStore, GlyphId, GlyphData}; +use text::shaping::ShaperMethods; +use text::util::{float_to_fixed, fixed_to_float}; + +use geom::Point2D; +use harfbuzz::{HB_MEMORY_MODE_READONLY, HB_DIRECTION_LTR}; +use harfbuzz::{hb_blob_create, hb_face_create_for_tables}; +use harfbuzz::{hb_blob_t}; +use harfbuzz::{hb_bool_t}; +use harfbuzz::{hb_buffer_add_utf8}; +use harfbuzz::{hb_buffer_destroy}; +use harfbuzz::{hb_buffer_get_glyph_positions}; +use harfbuzz::{hb_buffer_set_direction}; +use harfbuzz::{hb_face_destroy}; +use harfbuzz::{hb_face_t, hb_font_t}; +use harfbuzz::{hb_font_create}; +use harfbuzz::{hb_font_destroy, hb_buffer_create}; +use harfbuzz::{hb_font_funcs_create}; +use harfbuzz::{hb_font_funcs_destroy}; +use harfbuzz::{hb_font_funcs_set_glyph_func}; +use harfbuzz::{hb_font_funcs_set_glyph_h_advance_func}; +use harfbuzz::{hb_font_funcs_set_glyph_h_kerning_func}; +use harfbuzz::{hb_font_funcs_t, hb_buffer_t, hb_codepoint_t}; +use harfbuzz::{hb_font_set_funcs}; +use harfbuzz::{hb_font_set_ppem}; +use harfbuzz::{hb_font_set_scale}; +use harfbuzz::{hb_glyph_info_t}; +use harfbuzz::{hb_glyph_position_t}; +use harfbuzz::{hb_position_t, hb_tag_t}; +use harfbuzz::{hb_shape, hb_buffer_get_glyph_infos}; +use libc::{c_uint, c_int, c_void, c_char}; +use servo_util::geometry::Au; +use servo_util::range::Range; +use std::mem; +use std::char; +use std::cmp; +use std::ptr; + +static NO_GLYPH: i32 = -1; +static CONTINUATION_BYTE: i32 = -2; + +pub struct ShapedGlyphData { + count: int, + glyph_infos: *mut hb_glyph_info_t, + pos_infos: *mut hb_glyph_position_t, +} + +pub struct ShapedGlyphEntry { + codepoint: GlyphId, + advance: Au, + offset: Option<Point2D<Au>>, +} + +impl ShapedGlyphData { + pub fn new(buffer: *mut hb_buffer_t) -> ShapedGlyphData { + unsafe { + let mut glyph_count = 0; + let glyph_infos = hb_buffer_get_glyph_infos(buffer, &mut glyph_count); + let glyph_count = glyph_count as int; + assert!(glyph_infos.is_not_null()); + let mut pos_count = 0; + let pos_infos = hb_buffer_get_glyph_positions(buffer, &mut pos_count); + let pos_count = pos_count as int; + assert!(pos_infos.is_not_null()); + assert!(glyph_count == pos_count); + + ShapedGlyphData { + count: glyph_count, + glyph_infos: glyph_infos, + pos_infos: pos_infos, + } + } + } + + #[inline(always)] + fn byte_offset_of_glyph(&self, i: int) -> int { + assert!(i < self.count); + + unsafe { + let glyph_info_i = self.glyph_infos.offset(i); + (*glyph_info_i).cluster as int + } + } + + pub fn len(&self) -> int { + self.count + } + + /// Returns shaped glyph data for one glyph, and updates the y-position of the pen. + pub fn get_entry_for_glyph(&self, i: int, y_pos: &mut Au) -> ShapedGlyphEntry { + assert!(i < self.count); + + unsafe { + let glyph_info_i = self.glyph_infos.offset(i); + let pos_info_i = self.pos_infos.offset(i); + let x_offset = Shaper::fixed_to_float((*pos_info_i).x_offset); + let y_offset = Shaper::fixed_to_float((*pos_info_i).y_offset); + let x_advance = Shaper::fixed_to_float((*pos_info_i).x_advance); + let y_advance = Shaper::fixed_to_float((*pos_info_i).y_advance); + + let x_offset = Au::from_frac_px(x_offset); + let y_offset = Au::from_frac_px(y_offset); + let x_advance = Au::from_frac_px(x_advance); + let y_advance = Au::from_frac_px(y_advance); + + let offset = if x_offset == Au(0) && y_offset == Au(0) && y_advance == Au(0) { + None + } else { + // adjust the pen.. + if y_advance > Au(0) { + *y_pos = *y_pos - y_advance; + } + + Some(Point2D(x_offset, *y_pos - y_offset)) + }; + + ShapedGlyphEntry { + codepoint: (*glyph_info_i).codepoint as GlyphId, + advance: x_advance, + offset: offset, + } + } + } +} + +pub struct Shaper { + hb_face: *mut hb_face_t, + hb_font: *mut hb_font_t, + hb_funcs: *mut hb_font_funcs_t, +} + +#[unsafe_destructor] +impl Drop for Shaper { + fn drop(&mut self) { + unsafe { + assert!(self.hb_face.is_not_null()); + hb_face_destroy(self.hb_face); + + assert!(self.hb_font.is_not_null()); + hb_font_destroy(self.hb_font); + + assert!(self.hb_funcs.is_not_null()); + hb_font_funcs_destroy(self.hb_funcs); + } + } +} + +impl Shaper { + pub fn new(font: &mut Font) -> Shaper { + unsafe { + // Indirection for Rust Issue #6248, dynamic freeze scope artifically extended + let font_ptr = font as *mut Font; + let hb_face: *mut hb_face_t = hb_face_create_for_tables(get_font_table_func, + font_ptr as *mut c_void, + None); + let hb_font: *mut hb_font_t = hb_font_create(hb_face); + + // Set points-per-em. if zero, performs no hinting in that direction. + let pt_size = font.pt_size; + hb_font_set_ppem(hb_font, pt_size as c_uint, pt_size as c_uint); + + // Set scaling. Note that this takes 16.16 fixed point. + hb_font_set_scale(hb_font, + Shaper::float_to_fixed(pt_size) as c_int, + Shaper::float_to_fixed(pt_size) as c_int); + + // configure static function callbacks. + // NB. This funcs structure could be reused globally, as it never changes. + let hb_funcs: *mut hb_font_funcs_t = hb_font_funcs_create(); + hb_font_funcs_set_glyph_func(hb_funcs, glyph_func, ptr::mut_null(), None); + hb_font_funcs_set_glyph_h_advance_func(hb_funcs, glyph_h_advance_func, ptr::mut_null(), None); + hb_font_funcs_set_glyph_h_kerning_func(hb_funcs, glyph_h_kerning_func, ptr::mut_null(), ptr::mut_null()); + hb_font_set_funcs(hb_font, hb_funcs, font_ptr as *mut c_void, None); + + Shaper { + hb_face: hb_face, + hb_font: hb_font, + hb_funcs: hb_funcs, + } + } + } + + fn float_to_fixed(f: f64) -> i32 { + float_to_fixed(16, f) + } + + fn fixed_to_float(i: hb_position_t) -> f64 { + fixed_to_float(16, i) + } +} + +impl ShaperMethods for Shaper { + /// Calculate the layout metrics associated with the given text when rendered in a specific + /// font. + fn shape_text(&self, text: &str, glyphs: &mut GlyphStore) { + unsafe { + let hb_buffer: *mut hb_buffer_t = hb_buffer_create(); + hb_buffer_set_direction(hb_buffer, HB_DIRECTION_LTR); + + hb_buffer_add_utf8(hb_buffer, + text.as_ptr() as *const c_char, + text.len() as c_int, + 0, + text.len() as c_int); + + hb_shape(self.hb_font, hb_buffer, ptr::mut_null(), 0); + self.save_glyph_results(text, glyphs, hb_buffer); + hb_buffer_destroy(hb_buffer); + } + } +} + +impl Shaper { + fn save_glyph_results(&self, text: &str, glyphs: &mut GlyphStore, buffer: *mut hb_buffer_t) { + let glyph_data = ShapedGlyphData::new(buffer); + let glyph_count = glyph_data.len(); + let byte_max = text.len() as int; + let char_max = text.char_len() as int; + + // GlyphStore records are indexed by character, not byte offset. + // so, we must be careful to increment this when saving glyph entries. + let mut char_idx = CharIndex(0); + + assert!(glyph_count <= char_max); + + debug!("Shaped text[char count={}], got back {} glyph info records.", + char_max, + glyph_count); + + if char_max != glyph_count { + debug!("NOTE: Since these are not equal, we probably have been given some complex \ + glyphs."); + } + + // make map of what chars have glyphs + let mut byteToGlyph: Vec<i32>; + + // fast path: all chars are single-byte. + if byte_max == char_max { + byteToGlyph = Vec::from_elem(byte_max as uint, NO_GLYPH); + } else { + byteToGlyph = Vec::from_elem(byte_max as uint, CONTINUATION_BYTE); + for (i, _) in text.char_indices() { + *byteToGlyph.get_mut(i) = NO_GLYPH; + } + } + + debug!("(glyph idx) -> (text byte offset)"); + for i in range(0, glyph_data.len()) { + // loc refers to a *byte* offset within the utf8 string. + let loc = glyph_data.byte_offset_of_glyph(i); + if loc < byte_max { + assert!(*byteToGlyph.get(loc as uint) != CONTINUATION_BYTE); + *byteToGlyph.get_mut(loc as uint) = i as i32; + } else { + debug!("ERROR: tried to set out of range byteToGlyph: idx={}, glyph idx={}", + loc, + i); + } + debug!("{} -> {}", i, loc); + } + + debug!("text: {:s}", text); + debug!("(char idx): char->(glyph index):"); + for (i, ch) in text.char_indices() { + debug!("{}: {} --> {:d}", i, ch, *byteToGlyph.get(i) as int); + } + + // some helpers + let mut glyph_span: Range<int> = Range::empty(); + // this span contains first byte of first char, to last byte of last char in range. + // so, end() points to first byte of last+1 char, if it's less than byte_max. + let mut char_byte_span: Range<int> = Range::empty(); + let mut y_pos = Au(0); + + // main loop over each glyph. each iteration usually processes 1 glyph and 1+ chars. + // in cases with complex glyph-character assocations, 2+ glyphs and 1+ chars can be + // processed. + while glyph_span.begin() < glyph_count { + // start by looking at just one glyph. + glyph_span.extend_by(1); + debug!("Processing glyph at idx={}", glyph_span.begin()); + + let char_byte_start = glyph_data.byte_offset_of_glyph(glyph_span.begin()); + char_byte_span.reset(char_byte_start, 0); + + // find a range of chars corresponding to this glyph, plus + // any trailing chars that do not have associated glyphs. + while char_byte_span.end() < byte_max { + let range = text.char_range_at(char_byte_span.end() as uint); + drop(range.ch); + char_byte_span.extend_to(range.next as int); + + debug!("Processing char byte span: off={}, len={} for glyph idx={}", + char_byte_span.begin(), char_byte_span.length(), glyph_span.begin()); + + while char_byte_span.end() != byte_max && + byteToGlyph[char_byte_span.end() as uint] == NO_GLYPH { + debug!("Extending char byte span to include byte offset={} with no associated \ + glyph", char_byte_span.end()); + let range = text.char_range_at(char_byte_span.end() as uint); + drop(range.ch); + char_byte_span.extend_to(range.next as int); + } + + // extend glyph range to max glyph index covered by char_span, + // in cases where one char made several glyphs and left some unassociated chars. + let mut max_glyph_idx = glyph_span.end(); + for i in char_byte_span.each_index() { + if byteToGlyph[i as uint] > NO_GLYPH { + max_glyph_idx = cmp::max(byteToGlyph[i as uint] as int + 1, max_glyph_idx); + } + } + + if max_glyph_idx > glyph_span.end() { + glyph_span.extend_to(max_glyph_idx); + debug!("Extended glyph span (off={}, len={}) to cover char byte span's max \ + glyph index", + glyph_span.begin(), glyph_span.length()); + } + + + // if there's just one glyph, then we don't need further checks. + if glyph_span.length() == 1 { break; } + + // if no glyphs were found yet, extend the char byte range more. + if glyph_span.length() == 0 { continue; } + + debug!("Complex (multi-glyph to multi-char) association found. This case \ + probably doesn't work."); + + let mut all_glyphs_are_within_cluster: bool = true; + for j in glyph_span.each_index() { + let loc = glyph_data.byte_offset_of_glyph(j); + if !char_byte_span.contains(loc) { + all_glyphs_are_within_cluster = false; + break + } + } + + debug!("All glyphs within char_byte_span cluster?: {}", + all_glyphs_are_within_cluster); + + // found a valid range; stop extending char_span. + if all_glyphs_are_within_cluster { + break + } + } + + // character/glyph clump must contain characters. + assert!(char_byte_span.length() > 0); + // character/glyph clump must contain glyphs. + assert!(glyph_span.length() > 0); + + // now char_span is a ligature clump, formed by the glyphs in glyph_span. + // we need to find the chars that correspond to actual glyphs (char_extended_span), + //and set glyph info for those and empty infos for the chars that are continuations. + + // a simple example: + // chars: 'f' 't' 't' + // glyphs: 'ftt' '' '' + // cgmap: t f f + // gspan: [-] + // cspan: [-] + // covsp: [---------------] + + let mut covered_byte_span = char_byte_span.clone(); + // extend, clipping at end of text range. + while covered_byte_span.end() < byte_max + && byteToGlyph[covered_byte_span.end() as uint] == NO_GLYPH { + let range = text.char_range_at(covered_byte_span.end() as uint); + drop(range.ch); + covered_byte_span.extend_to(range.next as int); + } + + if covered_byte_span.begin() >= byte_max { + // oops, out of range. clip and forget this clump. + let end = glyph_span.end(); // FIXME: borrow checker workaround + glyph_span.reset(end, 0); + let end = char_byte_span.end(); // FIXME: borrow checker workaround + char_byte_span.reset(end, 0); + } + + // clamp to end of text. (I don't think this will be necessary, but..) + let end = covered_byte_span.end(); // FIXME: borrow checker workaround + covered_byte_span.extend_to(cmp::min(end, byte_max)); + + // fast path: 1-to-1 mapping of single char and single glyph. + if glyph_span.length() == 1 { + // TODO(Issue #214): cluster ranges need to be computed before + // shaping, and then consulted here. + // for now, just pretend that every character is a cluster start. + // (i.e., pretend there are no combining character sequences). + // 1-to-1 mapping of character to glyph also treated as ligature start. + let shape = glyph_data.get_entry_for_glyph(glyph_span.begin(), &mut y_pos); + let data = GlyphData::new(shape.codepoint, + shape.advance, + shape.offset, + false, + true, + true); + glyphs.add_glyph_for_char_index(char_idx, &data); + } else { + // collect all glyphs to be assigned to the first character. + let mut datas = vec!(); + + for glyph_i in glyph_span.each_index() { + let shape = glyph_data.get_entry_for_glyph(glyph_i, &mut y_pos); + datas.push(GlyphData::new(shape.codepoint, + shape.advance, + shape.offset, + false, // not missing + true, // treat as cluster start + glyph_i > glyph_span.begin())); + // all but first are ligature continuations + } + + // now add the detailed glyph entry. + glyphs.add_glyphs_for_char_index(char_idx, datas.as_slice()); + + // set the other chars, who have no glyphs + let mut i = covered_byte_span.begin(); + loop { + let range = text.char_range_at(i as uint); + drop(range.ch); + i = range.next as int; + if i >= covered_byte_span.end() { break; } + char_idx = char_idx + CharIndex(1); + glyphs.add_nonglyph_for_char_index(char_idx, false, false); + } + } + + // shift up our working spans past things we just handled. + let end = glyph_span.end(); // FIXME: borrow checker workaround + glyph_span.reset(end, 0); + let end = char_byte_span.end();; // FIXME: borrow checker workaround + char_byte_span.reset(end, 0); + char_idx = char_idx + CharIndex(1); + } + + // this must be called after adding all glyph data; it sorts the + // lookup table for finding detailed glyphs by associated char index. + glyphs.finalize_changes(); + } +} + +/// Callbacks from Harfbuzz when font map and glyph advance lookup needed. +extern fn glyph_func(_: *mut hb_font_t, + font_data: *mut c_void, + unicode: hb_codepoint_t, + _: hb_codepoint_t, + glyph: *mut hb_codepoint_t, + _: *mut c_void) + -> hb_bool_t { + let font: *const Font = font_data as *const Font; + assert!(font.is_not_null()); + + unsafe { + match (*font).glyph_index(char::from_u32(unicode).unwrap()) { + Some(g) => { + *glyph = g as hb_codepoint_t; + true as hb_bool_t + } + None => false as hb_bool_t + } + } +} + +extern fn glyph_h_advance_func(_: *mut hb_font_t, + font_data: *mut c_void, + glyph: hb_codepoint_t, + _: *mut c_void) + -> hb_position_t { + let font: *mut Font = font_data as *mut Font; + assert!(font.is_not_null()); + + unsafe { + let advance = (*font).glyph_h_advance(glyph as GlyphId); + Shaper::float_to_fixed(advance) + } +} + +extern fn glyph_h_kerning_func(_: *mut hb_font_t, + font_data: *mut c_void, + first_glyph: hb_codepoint_t, + second_glyph: hb_codepoint_t, + _: *mut c_void) + -> hb_position_t { + let font: *mut Font = font_data as *mut Font; + assert!(font.is_not_null()); + + unsafe { + let advance = (*font).glyph_h_kerning(first_glyph as GlyphId, second_glyph as GlyphId); + Shaper::float_to_fixed(advance) + } +} + +// Callback to get a font table out of a font. +extern fn get_font_table_func(_: *mut hb_face_t, tag: hb_tag_t, user_data: *mut c_void) -> *mut hb_blob_t { + unsafe { + let font: *const Font = user_data as *const Font; + assert!(font.is_not_null()); + + // TODO(Issue #197): reuse font table data, which will change the unsound trickery here. + match (*font).get_table_for_tag(tag as FontTableTag) { + None => ptr::mut_null(), + Some(ref font_table) => { + let skinny_font_table_ptr: *const FontTable = font_table; // private context + + let mut blob: *mut hb_blob_t = ptr::mut_null(); + (*skinny_font_table_ptr).with_buffer(|buf: *const u8, len: uint| { + // HarfBuzz calls `destroy_blob_func` when the buffer is no longer needed. + blob = hb_blob_create(buf as *const c_char, + len as c_uint, + HB_MEMORY_MODE_READONLY, + mem::transmute(skinny_font_table_ptr), + destroy_blob_func); + }); + + assert!(blob.is_not_null()); + blob + } + } + } +} + +// TODO(Issue #197): reuse font table data, which will change the unsound trickery here. +// In particular, we'll need to cast to a boxed, rather than owned, FontTable. + +// even better, should cache the harfbuzz blobs directly instead of recreating a lot. +extern fn destroy_blob_func(_: *mut c_void) { + // TODO: Previous code here was broken. Rewrite. +} diff --git a/components/gfx/text/shaping/mod.rs b/components/gfx/text/shaping/mod.rs new file mode 100644 index 00000000000..ef4bc2088f0 --- /dev/null +++ b/components/gfx/text/shaping/mod.rs @@ -0,0 +1,19 @@ +/* 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/. */ + +//! Shaper encapsulates a specific shaper, such as Harfbuzz, +//! Uniscribe, Pango, or Coretext. +//! +//! Currently, only harfbuzz bindings are implemented. + +use text::glyph::GlyphStore; + +pub use Shaper = text::shaping::harfbuzz::Shaper; + +pub mod harfbuzz; + +pub trait ShaperMethods { + fn shape_text(&self, text: &str, glyphs: &mut GlyphStore); +} + diff --git a/components/gfx/text/text_run.rs b/components/gfx/text/text_run.rs new file mode 100644 index 00000000000..70c10f1c64c --- /dev/null +++ b/components/gfx/text/text_run.rs @@ -0,0 +1,271 @@ +/* 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 font::{Font, RunMetrics, FontMetrics}; +use servo_util::geometry::Au; +use servo_util::range::Range; +use servo_util::vec::{Comparator, FullBinarySearchMethods}; +use std::slice::Items; +use sync::Arc; +use text::glyph::{CharIndex, GlyphStore}; +use font::FontHandleMethods; +use platform::font_template::FontTemplateData; + +/// A single "paragraph" of text in one font size and style. +#[deriving(Clone)] +pub struct TextRun { + pub text: Arc<String>, + pub font_template: Arc<FontTemplateData>, + pub pt_size: f64, + pub font_metrics: FontMetrics, + /// The glyph runs that make up this text run. + pub glyphs: Arc<Vec<GlyphRun>>, +} + +/// A single series of glyphs within a text run. +#[deriving(Clone)] +pub struct GlyphRun { + /// The glyphs. + glyph_store: Arc<GlyphStore>, + /// The range of characters in the containing run. + range: Range<CharIndex>, +} + +pub struct SliceIterator<'a> { + glyph_iter: Items<'a, GlyphRun>, + range: Range<CharIndex>, +} + +struct CharIndexComparator; + +impl Comparator<CharIndex,GlyphRun> for CharIndexComparator { + fn compare(&self, key: &CharIndex, value: &GlyphRun) -> Ordering { + if *key < value.range.begin() { + Less + } else if *key >= value.range.end() { + Greater + } else { + Equal + } + } +} + +impl<'a> Iterator<(&'a GlyphStore, CharIndex, Range<CharIndex>)> for SliceIterator<'a> { + // inline(always) due to the inefficient rt failures messing up inline heuristics, I think. + #[inline(always)] + fn next(&mut self) -> Option<(&'a GlyphStore, CharIndex, Range<CharIndex>)> { + let slice_glyphs = self.glyph_iter.next(); + if slice_glyphs.is_none() { + return None; + } + let slice_glyphs = slice_glyphs.unwrap(); + + let mut char_range = self.range.intersect(&slice_glyphs.range); + let slice_range_begin = slice_glyphs.range.begin(); + char_range.shift_by(-slice_range_begin); + if !char_range.is_empty() { + return Some((&*slice_glyphs.glyph_store, slice_range_begin, char_range)) + } + + return None; + } +} + +pub struct LineIterator<'a> { + range: Range<CharIndex>, + clump: Option<Range<CharIndex>>, + slices: SliceIterator<'a>, +} + +impl<'a> Iterator<Range<CharIndex>> for LineIterator<'a> { + fn next(&mut self) -> Option<Range<CharIndex>> { + // Loop until we hit whitespace and are in a clump. + loop { + match self.slices.next() { + Some((glyphs, offset, slice_range)) => { + match (glyphs.is_whitespace(), self.clump) { + (false, Some(ref mut c)) => { + c.extend_by(slice_range.length()); + } + (false, None) => { + let mut c = slice_range; + c.shift_by(offset); + self.clump = Some(c); + } + (true, None) => { /* chomp whitespace */ } + (true, Some(c)) => { + self.clump = None; + // The final whitespace clump is not included. + return Some(c); + } + } + }, + None => { + // flush any remaining chars as a line + if self.clump.is_some() { + let mut c = self.clump.take_unwrap(); + c.extend_to(self.range.end()); + return Some(c); + } else { + return None; + } + } + } + } + } +} + +impl<'a> TextRun { + pub fn new(font: &mut Font, text: String) -> TextRun { + let glyphs = TextRun::break_and_shape(font, text.as_slice()); + let run = TextRun { + text: Arc::new(text), + font_metrics: font.metrics.clone(), + font_template: font.handle.get_template(), + pt_size: font.pt_size, + glyphs: Arc::new(glyphs), + }; + return run; + } + + pub fn break_and_shape(font: &mut Font, text: &str) -> Vec<GlyphRun> { + // TODO(Issue #230): do a better job. See Gecko's LineBreaker. + let mut glyphs = vec!(); + let (mut byte_i, mut char_i) = (0u, CharIndex(0)); + let mut cur_slice_is_whitespace = false; + let (mut byte_last_boundary, mut char_last_boundary) = (0, CharIndex(0)); + while byte_i < text.len() { + let range = text.char_range_at(byte_i); + let ch = range.ch; + let next = range.next; + + // Slices alternate between whitespace and non-whitespace, + // representing line break opportunities. + let can_break_before = if cur_slice_is_whitespace { + match ch { + ' ' | '\t' | '\n' => false, + _ => { + cur_slice_is_whitespace = false; + true + } + } + } else { + match ch { + ' ' | '\t' | '\n' => { + cur_slice_is_whitespace = true; + true + }, + _ => false + } + }; + + // Create a glyph store for this slice if it's nonempty. + if can_break_before && byte_i > byte_last_boundary { + let slice = text.slice(byte_last_boundary, byte_i).to_string(); + debug!("creating glyph store for slice {} (ws? {}), {} - {} in run {}", + slice, !cur_slice_is_whitespace, byte_last_boundary, byte_i, text); + glyphs.push(GlyphRun { + glyph_store: font.shape_text(slice, !cur_slice_is_whitespace), + range: Range::new(char_last_boundary, char_i - char_last_boundary), + }); + byte_last_boundary = byte_i; + char_last_boundary = char_i; + } + + byte_i = next; + char_i = char_i + CharIndex(1); + } + + // Create a glyph store for the final slice if it's nonempty. + if byte_i > byte_last_boundary { + let slice = text.slice_from(byte_last_boundary).to_string(); + debug!("creating glyph store for final slice {} (ws? {}), {} - {} in run {}", + slice, cur_slice_is_whitespace, byte_last_boundary, text.len(), text); + glyphs.push(GlyphRun { + glyph_store: font.shape_text(slice, cur_slice_is_whitespace), + range: Range::new(char_last_boundary, char_i - char_last_boundary), + }); + } + + glyphs + } + + pub fn char_len(&self) -> CharIndex { + match self.glyphs.last() { + None => CharIndex(0), + Some(ref glyph_run) => glyph_run.range.end(), + } + } + + pub fn glyphs(&'a self) -> &'a Vec<GlyphRun> { + &*self.glyphs + } + + pub fn range_is_trimmable_whitespace(&self, range: &Range<CharIndex>) -> bool { + self.iter_slices_for_range(range).all(|(slice_glyphs, _, _)| { + slice_glyphs.is_whitespace() + }) + } + + pub fn ascent(&self) -> Au { + self.font_metrics.ascent + } + + pub fn descent(&self) -> Au { + self.font_metrics.descent + } + + pub fn advance_for_range(&self, range: &Range<CharIndex>) -> Au { + // TODO(Issue #199): alter advance direction for RTL + // TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text + self.iter_slices_for_range(range) + .fold(Au(0), |advance, (glyphs, _, slice_range)| { + advance + glyphs.advance_for_char_range(&slice_range) + }) + } + + pub fn metrics_for_range(&self, range: &Range<CharIndex>) -> RunMetrics { + RunMetrics::new(self.advance_for_range(range), + self.font_metrics.ascent, + self.font_metrics.descent) + } + + pub fn metrics_for_slice(&self, glyphs: &GlyphStore, slice_range: &Range<CharIndex>) -> RunMetrics { + RunMetrics::new(glyphs.advance_for_char_range(slice_range), + self.font_metrics.ascent, + self.font_metrics.descent) + } + + pub fn min_width_for_range(&self, range: &Range<CharIndex>) -> Au { + debug!("iterating outer range {:?}", range); + self.iter_slices_for_range(range).fold(Au(0), |max_piece_width, (_, offset, slice_range)| { + debug!("iterated on {:?}[{:?}]", offset, slice_range); + Au::max(max_piece_width, self.advance_for_range(&slice_range)) + }) + } + + /// Returns the index of the first glyph run containing the given character index. + fn index_of_first_glyph_run_containing(&self, index: CharIndex) -> Option<uint> { + self.glyphs.as_slice().binary_search_index_by(&index, CharIndexComparator) + } + + pub fn iter_slices_for_range(&'a self, range: &Range<CharIndex>) -> SliceIterator<'a> { + let index = match self.index_of_first_glyph_run_containing(range.begin()) { + None => self.glyphs.len(), + Some(index) => index, + }; + SliceIterator { + glyph_iter: self.glyphs.slice_from(index).iter(), + range: *range, + } + } + + pub fn iter_natural_lines_for_range(&'a self, range: &Range<CharIndex>) -> LineIterator<'a> { + LineIterator { + range: *range, + clump: None, + slices: self.iter_slices_for_range(range), + } + } +} diff --git a/components/gfx/text/util.rs b/components/gfx/text/util.rs new file mode 100644 index 00000000000..c5059bbff10 --- /dev/null +++ b/components/gfx/text/util.rs @@ -0,0 +1,285 @@ +/* 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 text::glyph::CharIndex; + +#[deriving(PartialEq)] +pub enum CompressionMode { + CompressNone, + CompressWhitespace, + CompressWhitespaceNewline, + DiscardNewline +} + +// ported from Gecko's nsTextFrameUtils::TransformText. +// +// High level TODOs: +// +// * Issue #113: consider incoming text state (arabic, etc) +// and propogate outgoing text state (dual of above) +// +// * Issue #114: record skipped and kept chars for mapping original to new text +// +// * Untracked: various edge cases for bidi, CJK, etc. +pub fn transform_text(text: &str, mode: CompressionMode, + incoming_whitespace: bool, + new_line_pos: &mut Vec<CharIndex>) -> (String, bool) { + let mut out_str = String::new(); + let out_whitespace = match mode { + CompressNone | DiscardNewline => { + let mut new_line_index = CharIndex(0); + for ch in text.chars() { + if is_discardable_char(ch, mode) { + // TODO: record skipped char + } else { + // TODO: record kept char + if ch == '\t' { + // TODO: set "has tab" flag + } else if ch == '\n' { + // Save new-line's position for line-break + // This value is relative(not absolute) + new_line_pos.push(new_line_index); + new_line_index = CharIndex(0); + } + + if ch != '\n' { + new_line_index = new_line_index + CharIndex(1); + } + out_str.push_char(ch); + } + } + text.len() > 0 && is_in_whitespace(text.char_at_reverse(0), mode) + }, + + CompressWhitespace | CompressWhitespaceNewline => { + let mut in_whitespace: bool = incoming_whitespace; + for ch in text.chars() { + // TODO: discard newlines between CJK chars + let mut next_in_whitespace: bool = is_in_whitespace(ch, mode); + + if !next_in_whitespace { + if is_always_discardable_char(ch) { + // revert whitespace setting, since this char was discarded + next_in_whitespace = in_whitespace; + // TODO: record skipped char + } else { + // TODO: record kept char + out_str.push_char(ch); + } + } else { /* next_in_whitespace; possibly add a space char */ + if in_whitespace { + // TODO: record skipped char + } else { + // TODO: record kept char + out_str.push_char(' '); + } + } + // save whitespace context for next char + in_whitespace = next_in_whitespace; + } /* /for str::each_char */ + in_whitespace + } + }; + + return (out_str.into_string(), out_whitespace); + + fn is_in_whitespace(ch: char, mode: CompressionMode) -> bool { + match (ch, mode) { + (' ', _) => true, + ('\t', _) => true, + ('\n', CompressWhitespaceNewline) => true, + (_, _) => false + } + } + + fn is_discardable_char(ch: char, mode: CompressionMode) -> bool { + if is_always_discardable_char(ch) { + return true; + } + match mode { + DiscardNewline | CompressWhitespaceNewline => ch == '\n', + _ => false + } + } + + fn is_always_discardable_char(_ch: char) -> bool { + // TODO: check for bidi control chars, soft hyphens. + false + } +} + +pub fn float_to_fixed(before: int, f: f64) -> i32 { + (1i32 << before as uint) * (f as i32) +} + +pub fn fixed_to_float(before: int, f: i32) -> f64 { + f as f64 * 1.0f64 / ((1i32 << before as uint) as f64) +} + +pub fn fixed_to_rounded_int(before: int, f: i32) -> int { + let half = 1i32 << (before-1) as uint; + if f > 0i32 { + ((half + f) >> before as uint) as int + } else { + -((half - f) >> before as uint) as int + } +} + +/* Generate a 32-bit TrueType tag from its 4 characters */ +pub fn true_type_tag(a: char, b: char, c: char, d: char) -> u32 { + let a = a as u32; + let b = b as u32; + let c = c as u32; + let d = d as u32; + (a << 24 | b << 16 | c << 8 | d) as u32 +} + +#[test] +fn test_true_type_tag() { + assert_eq!(true_type_tag('c', 'm', 'a', 'p'), 0x_63_6D_61_70_u32); +} + +#[test] +fn test_transform_compress_none() { + let test_strs = vec!( + " foo bar", + "foo bar ", + "foo\n bar", + "foo \nbar", + " foo bar \nbaz", + "foo bar baz", + "foobarbaz\n\n" + ); + let mode = CompressNone; + + for test in test_strs.iter() { + let mut new_line_pos = vec!(); + let (trimmed_str, _out) = transform_text(*test, mode, true, &mut new_line_pos); + assert_eq!(trimmed_str.as_slice(), *test) + } +} + +#[test] +fn test_transform_discard_newline() { + let test_strs = vec!( + " foo bar", + "foo bar ", + "foo\n bar", + "foo \nbar", + " foo bar \nbaz", + "foo bar baz", + "foobarbaz\n\n" + ); + + let oracle_strs = vec!( + " foo bar", + "foo bar ", + "foo bar", + "foo bar", + " foo bar baz", + "foo bar baz", + "foobarbaz" + ); + + assert_eq!(test_strs.len(), oracle_strs.len()); + let mode = DiscardNewline; + + for (test, oracle) in test_strs.iter().zip(oracle_strs.iter()) { + let mut new_line_pos = vec!(); + let (trimmed_str, _out) = transform_text(*test, mode, true, &mut new_line_pos); + assert_eq!(trimmed_str.as_slice(), *oracle) + } +} + +/* FIXME: Fix and re-enable +#[test] +fn test_transform_compress_whitespace() { + let test_strs : ~[String] = ~[" foo bar".to_string(), + "foo bar ".to_string(), + "foo\n bar".to_string(), + "foo \nbar".to_string(), + " foo bar \nbaz".to_string(), + "foo bar baz".to_string(), + "foobarbaz\n\n".to_string()]; + + let oracle_strs : ~[String] = ~[" foo bar".to_string(), + "foo bar ".to_string(), + "foo\n bar".to_string(), + "foo \nbar".to_string(), + " foo bar \nbaz".to_string(), + "foo bar baz".to_string(), + "foobarbaz\n\n".to_string()]; + + assert_eq!(test_strs.len(), oracle_strs.len()); + let mode = CompressWhitespace; + + for i in range(0, test_strs.len()) { + let mut new_line_pos = ~[]; + let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos); + assert_eq!(&trimmed_str, &oracle_strs[i]) + } +} + +#[test] +fn test_transform_compress_whitespace_newline() { + let test_strs : ~[String] = ~[" foo bar".to_string(), + "foo bar ".to_string(), + "foo\n bar".to_string(), + "foo \nbar".to_string(), + " foo bar \nbaz".to_string(), + "foo bar baz".to_string(), + "foobarbaz\n\n".to_string()]; + + let oracle_strs : ~[String] = ~["foo bar".to_string(), + "foo bar ".to_string(), + "foo bar".to_string(), + "foo bar".to_string(), + " foo bar baz".to_string(), + "foo bar baz".to_string(), + "foobarbaz ".to_string()]; + + assert_eq!(test_strs.len(), oracle_strs.len()); + let mode = CompressWhitespaceNewline; + + for i in range(0, test_strs.len()) { + let mut new_line_pos = ~[]; + let (trimmed_str, _out) = transform_text(test_strs[i], mode, true, &mut new_line_pos); + assert_eq!(&trimmed_str, &oracle_strs[i]) + } +} +*/ + +#[test] +fn test_transform_compress_whitespace_newline_no_incoming() { + let test_strs = vec!( + " foo bar", + "\nfoo bar", + "foo bar ", + "foo\n bar", + "foo \nbar", + " foo bar \nbaz", + "foo bar baz", + "foobarbaz\n\n" + ); + + let oracle_strs = vec!( + " foo bar", + " foo bar", + "foo bar ", + "foo bar", + "foo bar", + " foo bar baz", + "foo bar baz", + "foobarbaz " + ); + + assert_eq!(test_strs.len(), oracle_strs.len()); + let mode = CompressWhitespaceNewline; + + for (test, oracle) in test_strs.iter().zip(oracle_strs.iter()) { + let mut new_line_pos = vec!(); + let (trimmed_str, _out) = transform_text(*test, mode, false, &mut new_line_pos); + assert_eq!(trimmed_str.as_slice(), *oracle) + } +} |