mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
Cargoify servo
This commit is contained in:
parent
db2f642c32
commit
c6ab60dbfc
1761 changed files with 8423 additions and 2294 deletions
62
components/gfx/Cargo.toml
Normal file
62
components/gfx/Cargo.toml
Normal file
|
@ -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"
|
||||
|
||||
|
156
components/gfx/buffer_map.rs
Normal file
156
components/gfx/buffer_map.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
21
components/gfx/color.rs
Normal file
21
components/gfx/color.rs
Normal file
|
@ -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 }
|
||||
}
|
773
components/gfx/display_list/mod.rs
Normal file
773
components/gfx/display_list/mod.rs
Normal file
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
73
components/gfx/display_list/optimizer.rs
Normal file
73
components/gfx/display_list/optimizer.rs
Normal file
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
213
components/gfx/font.rs
Normal file
213
components/gfx/font.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
276
components/gfx/font_cache_task.rs
Normal file
276
components/gfx/font_cache_task.rs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
148
components/gfx/font_context.rs
Normal file
148
components/gfx/font_context.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
157
components/gfx/font_template.rs
Normal file
157
components/gfx/font_template.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
components/gfx/lib.rs
Normal file
72
components/gfx/lib.rs
Normal file
|
@ -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;
|
||||
|
297
components/gfx/platform/freetype/font.rs
Normal file
297
components/gfx/platform/freetype/font.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
82
components/gfx/platform/freetype/font_context.rs
Normal file
82
components/gfx/platform/freetype/font_context.rs
Normal file
|
@ -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 }),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
115
components/gfx/platform/freetype/font_list.rs
Normal file
115
components/gfx/platform/freetype/font_list.rs
Normal file
|
@ -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())
|
||||
}
|
35
components/gfx/platform/freetype/font_template.rs
Normal file
35
components/gfx/platform/freetype/font_template.rs
Normal file
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
185
components/gfx/platform/macos/font.rs
Normal file
185
components/gfx/platform/macos/font.rs
Normal file
|
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
16
components/gfx/platform/macos/font_context.rs
Normal file
16
components/gfx/platform/macos/font_context.rs
Normal file
|
@ -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: () }
|
||||
}
|
||||
}
|
37
components/gfx/platform/macos/font_list.rs
Normal file
37
components/gfx/platform/macos/font_list.rs
Normal file
|
@ -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())
|
||||
}
|
40
components/gfx/platform/macos/font_template.rs
Normal file
40
components/gfx/platform/macos/font_template.rs
Normal file
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
27
components/gfx/platform/mod.rs
Normal file
27
components/gfx/platform/mod.rs
Normal file
|
@ -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;
|
||||
}
|
419
components/gfx/render_context.rs
Normal file
419
components/gfx/render_context.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
443
components/gfx/render_task.rs
Normal file
443
components/gfx/render_task.rs
Normal file
|
@ -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));
|
||||
})
|
||||
}
|
||||
}
|
||||
|
752
components/gfx/text/glyph.rs
Normal file
752
components/gfx/text/glyph.rs
Normal file
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
18
components/gfx/text/mod.rs
Normal file
18
components/gfx/text/mod.rs
Normal file
|
@ -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;
|
||||
|
541
components/gfx/text/shaping/harfbuzz.rs
Normal file
541
components/gfx/text/shaping/harfbuzz.rs
Normal file
|
@ -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.
|
||||
}
|
19
components/gfx/text/shaping/mod.rs
Normal file
19
components/gfx/text/shaping/mod.rs
Normal file
|
@ -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);
|
||||
}
|
||||
|
271
components/gfx/text/text_run.rs
Normal file
271
components/gfx/text/text_run.rs
Normal file
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
285
components/gfx/text/util.rs
Normal file
285
components/gfx/text/util.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue