submodules: Split out GFX and all its dependencies into a servo-gfx submodule

This commit is contained in:
Patrick Walton 2012-11-11 19:42:44 -08:00
parent 6d4cb4319d
commit a38887194f
61 changed files with 177 additions and 5720 deletions

3
.gitmodules vendored
View file

@ -85,3 +85,6 @@
[submodule "src/skia"]
path = src/skia
url = git://github.com/mozilla-servo/skia.git
[submodule "src/servo-gfx"]
path = src/servo-gfx
url = git://github.com/mozilla-servo/servo-gfx.git

2
configure vendored
View file

@ -344,7 +344,7 @@ step_msg "running submodule autoconf scripts"
(cd ${CFG_SRC_DIR}src/mozjs/js/src && "${CFG_AUTOCONF213}") || exit $?
CFG_SUBMODULES="libwapcaplet rust-wapcaplet rust-harfbuzz rust-opengles skia rust-azure rust-cairo rust-stb-image rust-geom rust-glut rust-layers rust-http-client libparserutils libhubbub libcss rust-netsurfcss rust-css rust-hubbub sharegl rust-mozjs mozjs"
CFG_SUBMODULES="libwapcaplet rust-wapcaplet rust-harfbuzz rust-opengles skia rust-azure rust-cairo rust-stb-image rust-geom rust-glut rust-layers rust-http-client libparserutils libhubbub libcss rust-netsurfcss rust-css rust-hubbub sharegl rust-mozjs mozjs servo-gfx"
if [ $CFG_OSTYPE = "darwin" ]
then

View file

@ -81,6 +81,16 @@ DEPS_libcss += \
libparserutils \
$(NULL)
DEPS_servo-gfx += \
rust-azure \
rust-cairo \
rust-freetype \
rust-geom \
rust-harfbuzz \
rust-http-client \
rust-stb-image \
$(NULL)
# Platform-specific dependencies
ifeq ($(CFG_OSTYPE),darwin)
DEPS_rust-azure += \
@ -119,6 +129,12 @@ DEPS_rust-layers += \
rust-core-text \
$(NULL)
DEPS_servo-gfx += \
rust-core-foundation \
rust-core-graphics \
rust-core-text \
$(NULL)
endif
ifeq ($(CFG_OSTYPE),linux)

1
src/servo-gfx Submodule

@ -0,0 +1 @@
Subproject commit 08d11a2db06e733ce9c84524819d37486a26c160

View file

@ -3,51 +3,36 @@ The content task is the main task that runs JavaScript and spawns layout
tasks.
*/
export Content, ContentTask;
export ControlMsg, ExecuteMsg, ParseMsg, ExitMsg, Timer;
export PingMsg, PongMsg;
export task_from_context;
use core::util::replace;
use std::arc::{ARC, clone};
use comm::{Port, Chan, listen, select2};
use task::{spawn, spawn_listener};
use io::{read_whole_file, println};
use dom::bindings::utils::rust_box;
use dom::document::Document;
use dom::node::{Node, NodeScope, define_bindings};
use dom::event::{Event, ResizeEvent, ReflowEvent};
use dom::window::Window;
use geom::size::Size2D;
use layout::layout_task;
use layout_task::{LayoutTask, BuildMsg, BuildData, AddStylesheet};
use resource::image_cache_task::ImageCacheTask;
use layout::layout_task::{AddStylesheet, BuildData, BuildMsg, LayoutTask};
use newcss::stylesheet::Stylesheet;
use jsrt = js::rust::rt;
use js::rust::{cx, methods};
use js::global::{global_class, debug_fns};
use either::{Either, Left, Right};
use dom::bindings::utils::rust_box;
use js::rust::compartment;
use resource::resource_task;
use resource_task::{ResourceTask};
use std::net::url::Url;
use url_to_str = std::net::url::to_str;
use util::url::make_url;
use task::{task, SingleThreaded};
use std::cell::Cell;
use js::glue::bindgen::RUST_JSVAL_TO_OBJECT;
use core::comm::{Port, Chan, listen, select2};
use core::either;
use core::task::{SingleThreaded, spawn, spawn_listener, task};
use core::io::{println, read_whole_file};
use core::ptr::null;
use core::util::replace;
use geom::size::Size2D;
use gfx::resource::image_cache_task::ImageCacheTask;
use gfx::resource::resource_task::ResourceTask;
use gfx::util::url::make_url;
use js::JSVAL_NULL;
use js::global::{global_class, debug_fns};
use js::glue::bindgen::RUST_JSVAL_TO_OBJECT;
use js::jsapi::{JSContext, JSVal};
use js::jsapi::bindgen::{JS_CallFunctionValue, JS_GetContextPrivate};
use ptr::null;
use js::rust::{compartment, cx, methods};
use jsrt = js::rust::rt;
use newcss::stylesheet::Stylesheet;
use std::arc::{ARC, clone};
use std::cell::Cell;
use std::net::url::Url;
use url_to_str = std::net::url::to_str;
pub enum ControlMsg {
ParseMsg(Url),
@ -89,7 +74,7 @@ fn ContentTask(layout_task: LayoutTask,
return move control_chan;
}
struct Content {
pub struct Content {
layout_task: LayoutTask,
mut layout_join_port: Option<pipes::Port<()>>,
@ -159,7 +144,7 @@ fn Content(layout_task: LayoutTask,
content
}
fn task_from_context(cx: *JSContext) -> *Content unsafe {
pub fn task_from_context(cx: *JSContext) -> *Content unsafe {
cast::reinterpret_cast(&JS_GetContextPrivate(cx))
}

View file

@ -1,18 +1,18 @@
use content::content_task::{ContentTask, ExecuteMsg, ParseMsg, ExitMsg};
use content::content_task;
use dom::event::Event;
use gfx::compositor::Compositor;
use gfx::render_task::RenderTask;
use gfx::render_task;
use layout::layout_task;
use layout_task::LayoutTask;
use opts::Opts;
use resource::image_cache_task::{ImageCacheTask, ImageCacheTaskClient};
use resource::resource_task::ResourceTask;
use resource::resource_task;
use core::pipes::{Port, Chan};
use core::task::spawn_listener;
use gfx::compositor::Compositor;
use gfx::opts::Opts;
use gfx::render_task::RenderTask;
use gfx::render_task;
use std::cell::Cell;
use std::net::url::Url;

View file

@ -1,25 +0,0 @@
/* This file exists just to make it easier to import things inside of
./gfx/ 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. */
// shortcut names
pub use au = geometry;
pub use dl = display_list;
pub use display_list::DisplayItem;
pub use display_list::DisplayList;
pub use font::Font;
pub use font::FontDescriptor;
pub use font::FontGroup;
pub use font::FontSelector;
pub use font::FontStyle;
pub use font::RunMetrics;
pub use font_context::FontContext;
pub use font_list::FontFamily;
pub use font_list::FontList;
pub use geometry::Au;
pub use render_context::RenderContext;
pub use render_layers::RenderLayer;

View file

@ -1,93 +0,0 @@
Copyright (c) 2009, 2010, 2011 Daniel Johnson (<il.basso.buffo@gmail.com>).
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -1,18 +0,0 @@
use azure::AzFloat;
use AzColor = azure::azure_hl::Color;
pub type Color = AzColor;
pub fn rgb(r: u8, g: u8, b: u8) -> AzColor {
rgba(r, g, b, 1.0)
}
pub fn rgba(r: u8, g: u8, b: u8, a: float) -> 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: a as AzFloat
}
}

View file

@ -1,29 +0,0 @@
use azure::azure_hl::{DrawTarget};
use dom::event::Event;
use geom::rect::Rect;
struct LayerBuffer {
draw_target: DrawTarget,
// The rect in the containing RenderLayer that this represents.
rect: Rect<uint>,
// NB: stride is in pixels, like OpenGL GL_UNPACK_ROW_LENGTH.
stride: uint
}
/// A set of layer buffers. This is an atomic unit used to switch between the front and back
/// buffers.
struct LayerBufferSet {
buffers: ~[LayerBuffer]
}
/**
The interface used to by the renderer to aquire draw targets for
each rendered frame and submit them to be drawn to the display
*/
trait Compositor {
fn begin_drawing(next_dt: pipes::Chan<LayerBufferSet>);
fn draw(next_dt: pipes::Chan<LayerBufferSet>, +draw_me: LayerBufferSet);
}

View file

@ -1,121 +0,0 @@
use azure::azure_hl::DrawTarget;
use geom::Rect;
use geom::Point2D;
use gfx::{au, Au};
use color::{Color, rgb};
use image::base::Image;
use render_context::RenderContext;
use text::SendableTextRun;
use util::range::Range;
use std::arc::ARC;
use clone_arc = std::arc::clone;
use dvec::DVec;
pub use layout::display_list_builder::DisplayListBuilder;
struct DisplayItemData {
bounds : Rect<Au>, // TODO: whose coordinate system should this use?
}
impl DisplayItemData {
static pure fn new(bounds: &Rect<Au>) -> DisplayItemData {
DisplayItemData { bounds: copy *bounds }
}
}
pub enum DisplayItem {
SolidColor(DisplayItemData, Color),
// TODO: need to provide spacing data for text run.
// (i.e, to support rendering of CSS 'word-spacing' and 'letter-spacing')
// TODO: don't copy text runs, ever.
Text(DisplayItemData, ~SendableTextRun, Range, Color),
Image(DisplayItemData, ARC<~image::base::Image>),
Border(DisplayItemData, Au, Color)
}
impl DisplayItem {
pure fn d(&self) -> &self/DisplayItemData {
match *self {
SolidColor(ref d, _) => d,
Text(ref d, _, _, _) => d,
Image(ref d, _) => d,
Border(ref d, _, _) => d
}
}
fn draw_into_context(&self, ctx: &RenderContext) {
match *self {
SolidColor(_, color) => ctx.draw_solid_color(&self.d().bounds, color),
Text(_, run, range, color) => {
let new_run = @run.deserialize(ctx.font_ctx);
let font = new_run.font;
let origin = self.d().bounds.origin;
let baseline_origin = Point2D(origin.x, origin.y + font.metrics.ascent);
font.draw_text_into_context(ctx, new_run, range, baseline_origin, color);
},
Image(_, ref img) => ctx.draw_image(self.d().bounds, clone_arc(img)),
Border(_, width, color) => ctx.draw_border(&self.d().bounds, width, color),
}
debug!("%?", {
ctx.draw_border(&self.d().bounds, au::from_px(1), rgb(150, 150, 150));
() });
}
static pure fn new_SolidColor(bounds: &Rect<Au>, color: Color) -> DisplayItem {
SolidColor(DisplayItemData::new(bounds), color)
}
static pure fn new_Border(bounds: &Rect<Au>, width: Au, color: Color) -> DisplayItem {
Border(DisplayItemData::new(bounds), width, color)
}
static pure fn new_Text(bounds: &Rect<Au>,
run: ~SendableTextRun,
range: Range,
color: Color) -> DisplayItem {
Text(DisplayItemData::new(bounds), move run, range, color)
}
// ARC should be cloned into ImageData, but Images are not sendable
static pure fn new_Image(bounds: &Rect<Au>, image: ARC<~image::base::Image>) -> DisplayItem {
Image(DisplayItemData::new(bounds), move image)
}
}
// Dual-mode/freezable.
pub struct DisplayList {
list: ~[~DisplayItem]
}
trait DisplayListMethods {
fn append_item(&mut self, item: ~DisplayItem);
fn draw_into_context(ctx: &RenderContext);
}
impl DisplayList {
static fn new() -> DisplayList {
DisplayList { list: ~[] }
}
}
impl DisplayList : DisplayListMethods {
fn append_item(&mut self, item: ~DisplayItem) {
// FIXME(Issue #150): crashes
//debug!("Adding display item %u: %?", self.len(), item);
self.list.push(move item);
}
fn draw_into_context(ctx: &RenderContext) {
debug!("beginning display list");
for self.list.each |item| {
// FIXME(Issue #150): crashes
//debug!("drawing %?", *item);
item.draw_into_context(ctx);
}
debug!("ending display list");
}
}

View file

@ -1,500 +0,0 @@
use color::Color;
use gfx::au;
use gfx::{Au, RenderContext};
use geom::{Point2D, Rect, Size2D};
use util::range::Range;
use text::glyph::{GlyphStore, GlyphIndex};
use text::{
Shaper,
TextRun,
};
use azure::{AzFloat, AzScaledFontRef};
use azure::azure_hl::{BackendType, ColorPattern};
use core::dvec::DVec;
// 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.
#[cfg(target_os = "macos")]
pub type FontHandle/& = quartz::font::QuartzFontHandle;
#[cfg(target_os = "linux")]
pub type FontHandle/& = freetype::font::FreeTypeFontHandle;
pub trait FontHandleMethods {
pure fn face_name() -> ~str;
pure fn is_italic() -> bool;
pure fn boldness() -> CSSFontWeight;
fn glyph_index(codepoint: char) -> Option<GlyphIndex>;
fn glyph_h_advance(GlyphIndex) -> Option<FractionalPixel>;
fn get_metrics() -> FontMetrics;
}
// TODO: `new` should be part of trait FontHandleMethods
// TODO(Issue #163): this is a workaround for static methods and
// typedefs not working well together. It should be removed.
impl FontHandle {
#[cfg(target_os = "macos")]
static pub fn new(fctx: &native::FontContextHandle, buf: @~[u8], pt_size: float) -> Result<FontHandle, ()> {
quartz::font::QuartzFontHandle::new_from_buffer(fctx, buf, pt_size)
}
#[cfg(target_os = "linux")]
static pub fn new(fctx: &native::FontContextHandle, buf: @~[u8], pt_size: float) -> Result<FontHandle, ()> {
freetype::font::FreeTypeFontHandle::new(fctx, buf, pt_size)
}
}
// Used to abstract over the shaper's choice of fixed int representation.
type FractionalPixel = float;
struct FontMetrics {
underline_size: Au,
underline_offset: Au,
leading: Au,
x_height: Au,
em_size: Au,
ascent: Au,
descent: Au,
max_advance: Au
}
// TODO: use enum from CSS bindings
enum CSSFontWeight {
FontWeight100,
FontWeight200,
FontWeight300,
FontWeight400,
FontWeight500,
FontWeight600,
FontWeight700,
FontWeight800,
FontWeight900,
}
pub impl CSSFontWeight : cmp::Eq;
pub impl CSSFontWeight {
pub pure fn is_bold() -> bool {
match self {
FontWeight900 | FontWeight800 | FontWeight700 | FontWeight600 => true,
_ => false
}
}
}
// TODO: 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
pub struct FontStyle {
pt_size: float,
weight: CSSFontWeight,
italic: bool,
oblique: bool,
families: ~str,
// TODO: font-stretch, text-decoration, font-variant, size-adjust
}
// TODO(Issue #181): use deriving for trivial cmp::Eq implementations
pub impl FontStyle : cmp::Eq {
pure fn eq(other: &FontStyle) -> bool {
use std::cmp::FuzzyEq;
self.pt_size.fuzzy_eq(&other.pt_size) &&
self.weight == other.weight &&
self.italic == other.italic &&
self.oblique == other.oblique &&
self.families == other.families
}
pure fn ne(other: &FontStyle) -> bool { !self.eq(other) }
}
pub type SpecifiedFontStyle = FontStyle;
pub type UsedFontStyle = FontStyle;
// TODO: move me to layout
struct ResolvedFont {
group: @FontGroup,
style: SpecifiedFontStyle,
}
// FontDescriptor serializes a specific font and used font style
// options, such as point size.
// It's used to swizzle/unswizzle gfx::Font instances when
// communicating across tasks, such as the display list between layout
// and render tasks.
pub struct FontDescriptor {
style: UsedFontStyle,
selector: FontSelector,
}
// TODO(Issue #181): use deriving for trivial cmp::Eq implementations
pub impl FontDescriptor : cmp::Eq {
pure fn eq(other: &FontDescriptor) -> bool {
self.style == other.style &&
self.selector == other.selector
}
pure fn ne(other: &FontDescriptor) -> bool { !self.eq(other) }
}
pub impl FontDescriptor {
static pure fn new(style: &UsedFontStyle, selector: &FontSelector) -> FontDescriptor {
FontDescriptor {
style: copy *style,
selector: copy *selector,
}
}
}
// A FontSelector is a platform-specific strategy for serializing face names.
pub enum FontSelector {
SelectorPlatformName(~str),
SelectorStubDummy, // aka, use Josephin Sans
}
// TODO(Issue #181): use deriving for trivial cmp::Eq implementations
pub impl FontSelector : cmp::Eq {
pure fn eq(other: &FontSelector) -> bool {
match (&self, other) {
(&SelectorStubDummy, &SelectorStubDummy) => true,
(&SelectorPlatformName(a), &SelectorPlatformName(b)) => a == b,
_ => false
}
}
pure fn ne(other: &FontSelector) -> bool { !self.eq(other) }
}
// This struct is the result of mapping a specified FontStyle into the
// available fonts on the system. It contains an ordered list of font
// instances to be used in case the prior font cannot be used for
// rendering the specified language.
// The ordering of font instances is mainly decided by the CSS
// 'font-family' property. The last font is a system fallback font.
pub struct FontGroup {
families: @str,
// style of the first western font in group, which is
// used for purposes of calculating text run metrics.
style: UsedFontStyle,
fonts: ~[@Font],
}
pub impl FontGroup {
static fn new(families: @str, style: &UsedFontStyle, fonts: ~[@Font]) -> FontGroup {
FontGroup {
families: families,
style: copy *style,
fonts: move fonts,
}
}
}
struct RunMetrics {
// may be negative due to negative width (i.e., kerning of '.' in 'P.T.')
advance_width: Au,
ascent: Au, // nonzero
descent: Au, // nonzero
// this bounding box is relative to the left origin baseline.
// so, bounding_box.position.y = -ascent
bounding_box: Rect<Au>
}
/**
A font instance. Layout can use this to calculate glyph metrics
and the renderer can use it to render text.
*/
struct Font {
priv fontbuf: @~[u8],
priv handle: FontHandle,
priv mut azure_font: Option<AzScaledFontRef>,
priv mut shaper: Option<@Shaper>,
style: UsedFontStyle,
metrics: FontMetrics,
backend: BackendType,
drop {
use azure::bindgen::AzReleaseScaledFont;
do (copy self.azure_font).iter |fontref| { AzReleaseScaledFont(*fontref); }
}
}
impl Font {
// TODO: who should own fontbuf?
static fn new(fontbuf: @~[u8],
handle: FontHandle,
style: UsedFontStyle,
backend: BackendType) -> Font {
let metrics = handle.get_metrics();
Font {
fontbuf : fontbuf,
handle : move handle,
azure_font: None,
shaper: None,
style: move style,
metrics: move metrics,
backend: backend
}
}
priv fn get_shaper(@self) -> @Shaper {
// fast path: already created a shaper
match self.shaper {
Some(shaper) => { return shaper; },
None => {}
}
let shaper = @Shaper::new(self);
self.shaper = Some(shaper);
shaper
}
priv fn get_azure_font() -> AzScaledFontRef {
// fast path: we've already created the azure font resource
match self.azure_font {
Some(azfont) => return azfont,
None => {}
}
let ct_font = &self.handle.ctfont;
let size = self.style.pt_size as AzFloat;
let scaled_font = azure::scaled_font::ScaledFont::new(self.backend, ct_font, size);
let azure_scaled_font;
unsafe {
azure_scaled_font = scaled_font.azure_scaled_font;
cast::forget(move scaled_font);
}
self.azure_font = Some(azure_scaled_font);
azure_scaled_font
/*
// TODO: these cairo-related things should be in rust-cairo.
// creating a cairo font/face from a native font resource
// should be part of the NativeFont API, not exposed here.
#[cfg(target_os = "linux")]
fn get_cairo_face(font: &Font) -> *cairo_font_face_t {
use cairo::cairo_ft::bindgen::{cairo_ft_font_face_create_for_ft_face};
let ftface = font.handle.face;
let cface = cairo_ft_font_face_create_for_ft_face(ftface, 0 as c_int);
// FIXME: error handling
return cface;
}
*/
}
}
// Public API
pub trait FontMethods {
fn draw_text_into_context(rctx: &RenderContext,
run: &TextRun,
range: Range,
baseline_origin: Point2D<Au>,
color: Color);
fn measure_text(&TextRun, Range) -> RunMetrics;
fn shape_text(@self, &str) -> GlyphStore;
fn get_descriptor() -> FontDescriptor;
fn buf(&self) -> @~[u8];
// these are used to get glyphs and advances in the case that the
// shaper can't figure it out.
fn glyph_index(char) -> Option<GlyphIndex>;
fn glyph_h_advance(GlyphIndex) -> FractionalPixel;
}
pub impl Font : FontMethods {
fn draw_text_into_context(rctx: &RenderContext,
run: &TextRun,
range: Range,
baseline_origin: Point2D<Au>,
color: Color) {
use libc::types::common::c99::{uint16_t, uint32_t};
use azure::{AzDrawOptions,
AzGlyph,
AzGlyphBuffer};
use azure::bindgen::{AzCreateColorPattern,
AzDrawTargetFillGlyphs,
AzReleaseColorPattern};
let target = rctx.get_draw_target();
let azfont = self.get_azure_font();
let pattern = ColorPattern(color);
let azure_pattern = pattern.azure_color_pattern;
assert azure_pattern.is_not_null();
let options: AzDrawOptions = {
mAlpha: 1f as AzFloat,
fields: 0x0200 as uint16_t
};
let mut origin = copy baseline_origin;
let azglyphs = DVec();
azglyphs.reserve(range.length());
do run.glyphs.iter_glyphs_for_range(range) |_i, glyph| {
let glyph_advance = glyph.advance();
let glyph_offset = glyph.offset().get_default(au::zero_point());
let azglyph: AzGlyph = {
mIndex: glyph.index() as uint32_t,
mPosition: {
x: au::to_px(origin.x + glyph_offset.x) as AzFloat,
y: au::to_px(origin.y + glyph_offset.y) as AzFloat
}
};
origin = Point2D(origin.x + glyph_advance, origin.y);
azglyphs.push(move azglyph)
};
let azglyph_buf_len = azglyphs.len();
let azglyph_buf = dvec::unwrap(move azglyphs);
let glyphbuf: AzGlyphBuffer = unsafe {{
mGlyphs: vec::raw::to_ptr(azglyph_buf),
mNumGlyphs: azglyph_buf_len as uint32_t
}};
// TODO: this call needs to move into azure_hl.rs
AzDrawTargetFillGlyphs(target.azure_draw_target,
azfont,
ptr::to_unsafe_ptr(&glyphbuf),
azure_pattern,
ptr::to_unsafe_ptr(&options),
ptr::null());
}
fn measure_text(run: &TextRun, range: Range) -> RunMetrics {
assert range.is_valid_for_string(run.text);
// TODO: alter advance direction for RTL
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
let mut advance = Au(0);
for run.glyphs.iter_glyphs_for_range(range) |_i, glyph| {
advance += glyph.advance();
}
let mut bounds = Rect(Point2D(Au(0), -self.metrics.ascent),
Size2D(advance, self.metrics.ascent + self.metrics.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.
let metrics = RunMetrics { advance_width: advance,
bounding_box: bounds,
ascent: self.metrics.ascent,
descent: self.metrics.descent,
};
debug!("Measured text range '%s' with metrics:", run.text.substr(range.begin(), range.length()));
debug!("%?", metrics);
return metrics;
}
fn shape_text(@self, text: &str) -> GlyphStore {
let store = GlyphStore(text.len());
let shaper = self.get_shaper();
shaper.shape_text(text, &store);
return move store;
}
fn get_descriptor() -> FontDescriptor {
// TODO(Issue #174): implement by-platform-name FontSelectors,
// probably by adding such an API to FontHandle.
FontDescriptor::new(&font_context::dummy_style(), &SelectorStubDummy)
}
fn buf(&self) -> @~[u8] {
self.fontbuf
}
fn glyph_index(codepoint: char) -> Option<GlyphIndex> {
self.handle.glyph_index(codepoint)
}
fn glyph_h_advance(glyph: GlyphIndex) -> FractionalPixel {
match self.handle.glyph_h_advance(glyph) {
Some(adv) => adv,
None => /* FIXME: Need fallback strategy */ 10f as FractionalPixel
}
}
}
/*fn should_destruct_on_fail_without_leaking() {
#[test];
#[should_fail];
let fctx = @FontContext();
let matcher = @FontMatcher(fctx);
let _font = matcher.get_test_font();
fail;
}
fn should_get_glyph_indexes() {
#[test];
let fctx = @FontContext();
let matcher = @FontMatcher(fctx);
let font = matcher.get_test_font();
let glyph_idx = font.glyph_index('w');
assert glyph_idx == Some(40u as GlyphIndex);
}
fn should_get_glyph_advance() {
#[test];
#[ignore];
let fctx = @FontContext();
let matcher = @FontMatcher(fctx);
let font = matcher.get_test_font();
let x = font.glyph_h_advance(40u as GlyphIndex);
assert x == 15f || x == 16f;
}
// Testing thread safety
fn should_get_glyph_advance_stress() {
#[test];
#[ignore];
let mut ports = ~[];
for iter::repeat(100) {
let (chan, port) = pipes::stream();
ports += [@move port];
do task::spawn |move chan| {
let fctx = @FontContext();
let matcher = @FontMatcher(fctx);
let _font = matcher.get_test_font();
let x = font.glyph_h_advance(40u as GlyphIndex);
assert x == 15f || x == 16f;
chan.send(());
}
}
for ports.each |port| {
port.recv();
}
}
fn should_be_able_to_create_instances_in_multiple_threads() {
#[test];
for iter::repeat(10u) {
do task::spawn {
let fctx = @FontContext();
let matcher = @FontMatcher(fctx);
let _font = matcher.get_test_font();
}
}
}
*/

View file

@ -1,123 +0,0 @@
use gfx::{FontDescriptor, FontList, FontSelector, FontStyle};
use gfx::font::{SelectorPlatformName, SelectorStubDummy, SpecifiedFontStyle};
use gfx::native::FontHandle;
use util::cache;
use azure::azure_hl::BackendType;
use core::dvec::DVec;
// TODO(Issue #164): delete, and get default font from font list
const TEST_FONT: [u8 * 33004] = #include_bin("JosefinSans-SemiBold.ttf");
fn test_font_bin() -> ~[u8] {
return vec::from_fn(33004, |i| TEST_FONT[i]);
}
// TODO(Rust #3934): creating lots of new dummy styles is a workaround
// for not being able to store symbolic enums in top-level constants.
pub fn dummy_style() -> FontStyle {
use gfx::font::FontWeight300;
return FontStyle {
pt_size: 20f,
weight: FontWeight300,
italic: false,
oblique: false,
families: ~"Helvetica, serif",
}
}
// TODO(Issue #163): this is a workaround for static methods and
// typedefs not working well together. It should be removed.
#[cfg(target_os = "macos")]
type FontContextHandle/& = quartz::font_context::QuartzFontContextHandle;
#[cfg(target_os = "linux")]
type FontContextHandle/& = freetype::font_context::FreeTypeFontContextHandle;
pub impl FontContextHandle {
#[cfg(target_os = "macos")]
static pub fn new() -> FontContextHandle {
quartz::font_context::QuartzFontContextHandle::new()
}
#[cfg(target_os = "linux")]
static pub fn new() -> FontContextHandle {
freetype::font_context::FreeTypeFontContextHandle::new()
}
}
pub struct FontContext {
instance_cache: cache::MonoCache<FontDescriptor, @Font>,
font_list: Option<FontList>, // only needed by layout
handle: FontContextHandle,
backend: BackendType,
}
pub impl FontContext {
static fn new(backend: BackendType, needs_font_list: bool) -> FontContext {
let handle = FontContextHandle::new();
let font_list = if needs_font_list { Some(FontList::new(&handle)) } else { None };
FontContext {
// TODO(Rust #3902): remove extraneous type parameters once they are inferred correctly.
instance_cache: cache::new::<FontDescriptor, @Font, cache::MonoCache<FontDescriptor, @Font>>(10),
font_list: move font_list,
handle: move handle,
backend: backend
}
}
fn get_resolved_font_for_style(style: &SpecifiedFontStyle) -> @FontGroup {
// TODO(Issue #178, E): implement a cache of FontGroup instances.
self.create_font_group(style)
}
fn get_font_by_descriptor(desc: &FontDescriptor) -> Result<@Font, ()> {
match self.instance_cache.find(desc) {
Some(f) => Ok(f),
None => {
let result = self.create_font_instance(desc);
match result {
Ok(font) => {
self.instance_cache.insert(desc, font);
}, _ => {}
};
result
}
}
}
priv fn create_font_group(style: &SpecifiedFontStyle) -> @FontGroup {
// TODO(Issue #178, D): implement private font matching
// TODO(Issue #174): implement by-platform-name FontSelectors
let desc = FontDescriptor::new(&font_context::dummy_style(), &SelectorStubDummy);
let fonts = DVec();
match self.get_font_by_descriptor(&desc) {
Ok(instance) => fonts.push(instance),
Err(()) => {}
}
assert fonts.len() > 0;
// TODO(Issue #179): Split FontStyle into specified and used styles
let used_style = copy *style;
@FontGroup::new(style.families.to_managed(), &used_style, dvec::unwrap(move fonts))
}
priv fn create_font_instance(desc: &FontDescriptor) -> Result<@Font, ()> {
match desc.selector {
SelectorStubDummy => {
let font_bin = @test_font_bin();
let handle = FontHandle::new(&self.handle, font_bin, desc.style.pt_size);
let handle = if handle.is_ok() {
result::unwrap(move handle)
} else {
return Err(handle.get_err());
};
return Ok(@Font::new(font_bin, move handle, copy desc.style, self.backend));
},
// TODO(Issue #174): implement by-platform-name font selectors.
SelectorPlatformName(_) => { fail ~"FontContext::create_font_instance() can't yet handle SelectorPlatformName." }
}
}
}

View file

@ -1,140 +0,0 @@
use gfx::font::{
CSSFontWeight,
SpecifiedFontStyle,
};
use gfx::native::FontHandle;
use dvec::DVec;
use send_map::{linear, SendMap};
#[cfg(target_os = "macos")]
type FontListHandle/& = quartz::font_list::QuartzFontListHandle;
#[cfg(target_os = "linux")]
type FontListHandle/& = freetype::font_list::FreeTypeFontListHandle;
pub impl FontListHandle {
#[cfg(target_os = "macos")]
static pub fn new(fctx: &native::FontContextHandle) -> Result<FontListHandle, ()> {
Ok(quartz::font_list::QuartzFontListHandle::new(fctx))
}
#[cfg(target_os = "linux")]
static pub fn new(fctx: &native::FontContextHandle) -> Result<FontListHandle, ()> {
Ok(freetype::font_list::FreeTypeFontListHandle::new(fctx))
}
}
type FontFamilyMap = linear::LinearMap<~str, @FontFamily>;
pub struct FontList {
mut family_map: FontFamilyMap,
mut handle: FontListHandle,
}
pub impl FontList {
static fn new(fctx: &native::FontContextHandle) -> FontList {
let handle = result::unwrap(FontListHandle::new(fctx));
let list = FontList {
handle: move handle,
family_map: linear::LinearMap(),
};
list.refresh(fctx);
return move list;
}
priv fn refresh(fctx: &native::FontContextHandle) {
// TODO(Issue #186): don't refresh unless something actually
// changed. Does OSX have a notification for this event?
do util::time::time("gfx::font_list: regenerating available font families and faces") {
self.family_map = self.handle.get_available_families(fctx);
}
}
fn find_font_in_family(family_name: &str,
style: &SpecifiedFontStyle) -> Option<@FontEntry> {
let family = self.find_family(family_name);
let mut result : Option<@FontEntry> = None;
// if such family exists, try to match style to a font
do family.iter |fam| {
result = fam.find_font_for_style(style);
}
return result;
}
priv fn find_family(family_name: &str) -> Option<@FontFamily> {
// look up canonical name
let family = self.family_map.find(&str::from_slice(family_name));
// TODO(Issue #188): look up localized font family names if canonical name not found
return family;
}
}
// Holds a specific font family, and the various
pub struct FontFamily {
family_name: @str,
entries: DVec<@FontEntry>,
}
pub impl FontFamily {
static fn new(family_name: &str) -> FontFamily {
FontFamily {
family_name: str::from_slice(family_name).to_managed(),
entries: DVec(),
}
}
pure fn find_font_for_style(style: &SpecifiedFontStyle) -> Option<@FontEntry> {
assert self.entries.len() > 0;
// 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 self.entries.each |entry| {
if (style.weight.is_bold() == entry.is_bold()) &&
(style.italic == entry.is_italic()) {
return Some(*entry);
}
}
return None;
}
}
// This struct summarizes an available font's features. In the future,
// this will include fiddly settings such as special font table handling.
// In the common case, each FontFamily will have a singleton FontEntry, or
// it will have the standard four faces: Normal, Bold, Italic, BoldItalic.
struct FontEntry {
family: @FontFamily,
face_name: ~str,
priv weight: CSSFontWeight,
priv italic: bool,
handle: FontHandle,
// TODO: array of OpenType features, etc.
}
impl FontEntry {
static fn new(family: @FontFamily, handle: FontHandle) -> FontEntry {
FontEntry {
family: family,
face_name: handle.face_name(),
weight: handle.boldness(),
italic: handle.is_italic(),
handle: move handle
}
}
pure fn is_bold() -> bool {
self.weight.is_bold()
}
pure fn is_italic() -> bool { self.italic }
}

View file

@ -1,12 +0,0 @@
pub struct FontconfigFontContextHandle {
ctx: (),
drop { }
}
pub impl FontconfigFontContextHandle {
// this is a placeholder.
static pub fn new() -> FontconfigFontContextHandle {
FontconfigFontContextHandle { ctx: () }
}
}

View file

@ -1,152 +0,0 @@
extern mod freetype;
use font::{FontMetrics, FractionalPixel};
use font_context::FreeTypeFontContext;
use gfx::geometry;
use gfx::geometry::Au;
use vec_as_buf = vec::as_imm_buf;
use ptr::{addr_of, null};
use cast::reinterpret_cast;
use glyph::GlyphIndex;
use servo_util::text::{float_to_fixed, fixed_to_float};
use freetype::{ FT_Error, FT_Library, FT_Face, FT_Long, FT_ULong, FT_Size, FT_SizeRec,
FT_UInt, FT_GlyphSlot, FT_Size_Metrics, FT_FaceRec, FT_F26Dot6 };
use freetype::bindgen::{
FT_Init_FreeType,
FT_Done_FreeType,
FT_New_Memory_Face,
FT_Done_Face,
FT_Get_Char_Index,
FT_Load_Glyph,
FT_Set_Char_Size
};
fn float_to_fixed_ft(f: float) -> i32 {
float_to_fixed(6, f)
}
fn fixed_to_float_ft(f: i32) -> float {
fixed_to_float(6, f)
}
pub struct FreeTypeFontHandle {
/// The font binary. This must stay valid for the lifetime of the font
buf: @~[u8],
face: FT_Face,
drop {
assert self.face.is_not_null();
if !FT_Done_Face(self.face).succeeded() {
fail ~"FT_Done_Face failed";
}
}
}
pub impl FreeTypeFontHandle {
static pub fn new(fctx: &FreeTypeFontContext,
buf: @~[u8], pt_size: float) -> Result<FreeTypeFontHandle, ()> {
let ft_ctx = fctx.ctx;
assert ft_ctx.is_not_null();
let face: FT_Face = null();
return vec_as_buf(*buf, |cbuf, _len| {
if FT_New_Memory_Face(ft_ctx, cbuf, (*buf).len() as FT_Long,
0 as FT_Long, addr_of(&face)).succeeded() {
let res = FT_Set_Char_Size(face, // the face
float_to_fixed_ft(pt_size) as FT_F26Dot6, // char width
float_to_fixed_ft(pt_size) as FT_F26Dot6, // char height
72, // horiz. DPI
72); // vert. DPI
if !res.succeeded() { fail ~"unable to set font char size" }
Ok(FreeTypeFontHandle { face: face, buf: buf })
} else {
Err(())
}
})
}
pub fn glyph_index(codepoint: char) -> Option<GlyphIndex> {
assert self.face.is_not_null();
let idx = FT_Get_Char_Index(self.face, codepoint as FT_ULong);
return if idx != 0 as FT_UInt {
Some(idx as GlyphIndex)
} else {
debug!("Invalid codepoint: %?", codepoint);
None
};
}
pub fn glyph_h_advance(glyph: GlyphIndex) -> Option<FractionalPixel> {
assert self.face.is_not_null();
let res = FT_Load_Glyph(self.face, glyph as FT_UInt, 0);
if res.succeeded() {
unsafe {
let void_glyph = (*self.face).glyph;
let slot: FT_GlyphSlot = reinterpret_cast(&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;
}
}
pub fn get_metrics() -> FontMetrics {
/* TODO(Issue #76): complete me */
let face = self.get_face_rec();
let underline_size = self.font_units_to_au(face.underline_thickness as float);
let underline_offset = self.font_units_to_au(face.underline_position as float);
let em_size = self.font_units_to_au(face.units_per_EM as float);
let ascent = self.font_units_to_au(face.ascender as float);
let descent = self.font_units_to_au(face.descender as float);
let max_advance = self.font_units_to_au(face.max_advance_width as float);
return FontMetrics {
underline_size: underline_size,
underline_offset: underline_offset,
leading: geometry::from_pt(0.0), //FIXME
x_height: geometry::from_pt(0.0), //FIXME
em_size: em_size,
ascent: ascent,
descent: descent,
max_advance: max_advance
}
}
priv fn get_face_rec() -> &self/FT_FaceRec unsafe {
&(*self.face)
}
priv fn font_units_to_au(value: float) -> 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 { cast::transmute(&(*face.size)) };
let metrics: &FT_Size_Metrics = unsafe { &((*size).metrics) };
let em_size = face.units_per_EM as float;
let x_scale = (metrics.x_ppem as float) / em_size as float;
// 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);
}
}
trait FTErrorMethods {
fn succeeded() -> bool;
}
impl FT_Error : FTErrorMethods {
fn succeeded() -> bool { self == 0 as FT_Error }
}

View file

@ -1,33 +0,0 @@
extern mod freetype;
use freetype::{
FT_Error,
FT_Library,
};
use freetype::bindgen::{
FT_Init_FreeType,
FT_Done_FreeType
};
pub struct FreeTypeFontContextHandle {
ctx: FT_Library,
drop {
assert self.ctx.is_not_null();
FT_Done_FreeType(self.ctx);
}
}
pub impl FreeTypeFontContextHandle {
static pub fn new() -> FreeTypeFontContextHandle {
let lib: FT_Library = ptr::null();
let res = FT_Init_FreeType(ptr::addr_of(&lib));
// FIXME: error handling
assert res == 0 as FT_Error;
FreeTypeFontContext {
ctx: lib,
}
}
}

View file

@ -1,80 +0,0 @@
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use num::{Num, from_int};
pub enum Au = i32;
impl Au : Num {
pure fn add(other: &Au) -> Au { Au(*self + **other) }
pure fn sub(other: &Au) -> Au { Au(*self - **other) }
pure fn mul(other: &Au) -> Au { Au(*self * **other) }
pure fn div(other: &Au) -> Au { Au(*self / **other) }
pure fn modulo(other: &Au) -> Au { Au(*self % **other) }
pure fn neg() -> Au { Au(-*self) }
pure fn to_int() -> int { *self as int }
static pure fn from_int(n: int) -> Au {
Au((n & (i32::max_value as int)) as i32)
}
}
impl Au : cmp::Ord {
pure fn lt(other: &Au) -> bool { *self < **other }
pure fn le(other: &Au) -> bool { *self <= **other }
pure fn ge(other: &Au) -> bool { *self >= **other }
pure fn gt(other: &Au) -> bool { *self > **other }
}
impl Au : cmp::Eq {
pure fn eq(other: &Au) -> bool { *self == **other }
pure fn ne(other: &Au) -> bool { *self != **other }
}
pub pure fn min(x: Au, y: Au) -> Au { if x < y { x } else { y } }
pub pure fn max(x: Au, y: Au) -> Au { if x > y { x } else { y } }
pub fn box<A:Copy Num>(x: A, y: A, w: A, h: A) -> Rect<A> {
Rect(Point2D(x, y), Size2D(w, h))
}
impl Au {
pub fn scale_by(factor: float) -> Au {
Au(((*self as float) * factor) as i32)
}
}
pub pure fn zero_rect() -> Rect<Au> {
let z = Au(0);
Rect(Point2D(z, z), Size2D(z, z))
}
pub pure fn zero_point() -> Point2D<Au> {
Point2D(Au(0), Au(0))
}
pub pure fn zero_size() -> Size2D<Au> {
Size2D(Au(0), Au(0))
}
pub pure fn from_frac_px(f: float) -> Au {
Au((f * 60f) as i32)
}
pub pure fn from_px(i: int) -> Au {
from_int(i * 60)
}
pub pure fn to_px(au: Au) -> int {
(*au / 60) as int
}
pub pure fn to_frac_px(au: Au) -> float {
(*au as float) / 60f
}
// assumes 72 points per inch, and 96 px per inch
pub pure fn from_pt(f: float) -> Au {
from_px((f / 72f * 96f) as int)
}

View file

@ -1,9 +0,0 @@
/* This file exists just to make it easier to import platform-specific
implementations.
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 gfx::font::FontHandle;
pub use gfx::font_context::FontContextHandle;
pub use gfx::font_list::FontListHandle;

View file

@ -1,190 +0,0 @@
extern mod core_foundation;
extern mod core_graphics;
extern mod core_text;
use font_context::QuartzFontContextHandle;
use gfx::au;
use gfx::font::{
CSSFontWeight,
FontHandleMethods,
FontMetrics,
FontWeight100,
FontWeight200,
FontWeight300,
FontWeight400,
FontWeight500,
FontWeight600,
FontWeight700,
FontWeight800,
FontWeight900,
FractionalPixel
};
use text::glyph::GlyphIndex;
use libc::size_t;
use cf = core_foundation;
use cf::base::{
CFIndex,
CFRelease,
CFTypeRef
};
use cf::string::UniChar;
use cg = core_graphics;
use cg::base::{CGFloat, CGAffineTransform};
use cg::data_provider::{
CGDataProviderRef, CGDataProvider
};
use cg::font::{
CGFontCreateWithDataProvider,
CGFontRef,
CGFontRelease,
CGGlyph,
};
use cg::geometry::CGRect;
use ct = core_text;
use ct::font::CTFont;
use ct::font_descriptor::{
kCTFontDefaultOrientation,
CTFontSymbolicTraits,
SymbolicTraitAccessors,
};
pub struct QuartzFontHandle {
priv mut cgfont: Option<CGFontRef>,
ctfont: CTFont,
drop {
// TODO(Issue #152): use a wrapped CGFont.
do (copy self.cgfont).iter |cgfont| {
assert cgfont.is_not_null();
CGFontRelease(*cgfont);
}
}
}
pub impl QuartzFontHandle {
static fn new_from_buffer(_fctx: &QuartzFontContextHandle, buf: @~[u8], pt_size: float) -> Result<QuartzFontHandle, ()> {
let fontprov = vec::as_imm_buf(*buf, |cbuf, len| {
CGDataProvider::new_from_buffer(cbuf, len)
});
let cgfont = CGFontCreateWithDataProvider(fontprov.get_ref());
if cgfont.is_null() { return Err(()); }
let ctfont = CTFont::new_from_CGFont(cgfont, pt_size);
let result = Ok(QuartzFontHandle {
cgfont : Some(cgfont),
ctfont : move ctfont,
});
return move result;
}
static fn new_from_CTFont(_fctx: &QuartzFontContextHandle, ctfont: CTFont) -> Result<QuartzFontHandle, ()> {
let result = Ok(QuartzFontHandle {
mut cgfont: None,
ctfont: move ctfont,
});
return move result;
}
fn get_CGFont() -> CGFontRef {
match self.cgfont {
Some(cg) => cg,
None => {
let cgfont = self.ctfont.copy_to_CGFont();
self.cgfont = Some(cgfont);
cgfont
}
}
}
}
pub impl QuartzFontHandle : FontHandleMethods {
pure fn family_name() -> ~str {
self.ctfont.family_name()
}
pure fn face_name() -> ~str {
self.ctfont.face_name()
}
pure fn is_italic() -> bool {
self.ctfont.symbolic_traits().is_italic()
}
pure fn boldness() -> CSSFontWeight {
// -1.0 to 1.0
let normalized = unsafe { 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 FontWeight100; }
if normalized < 2.0 { return FontWeight200; }
if normalized < 3.0 { return FontWeight300; }
if normalized < 4.0 { return FontWeight400; }
if normalized < 5.0 { return FontWeight500; }
if normalized < 6.0 { return FontWeight600; }
if normalized < 7.0 { return FontWeight700; }
if normalized < 8.0 { return FontWeight800; }
else { return FontWeight900; }
}
fn glyph_index(codepoint: char) -> Option<GlyphIndex> {
let characters: ~[UniChar] = ~[codepoint as UniChar];
let glyphs: ~[mut CGGlyph] = ~[mut 0 as CGGlyph];
let count: CFIndex = 1;
let result = do vec::as_imm_buf(characters) |character_buf, _l| {
do vec::as_imm_buf(glyphs) |glyph_buf, _l| {
self.ctfont.get_glyphs_for_characters(character_buf, glyph_buf, count)
}
};
if !result {
// No glyph for this character
return None;
}
assert glyphs[0] != 0; // FIXME: error handling
return Some(glyphs[0] as GlyphIndex);
}
fn glyph_h_advance(glyph: GlyphIndex) -> Option<FractionalPixel> {
let glyphs = ~[glyph as CGGlyph];
let advance = do vec::as_imm_buf(glyphs) |glyph_buf, _l| {
self.ctfont.get_advances_for_glyphs(kCTFontDefaultOrientation, glyph_buf, ptr::null(), 1)
};
return Some(advance as FractionalPixel);
}
fn get_metrics() -> FontMetrics {
let bounding_rect: CGRect = self.ctfont.bounding_box();
let ascent = au::from_pt(self.ctfont.ascent() as float);
let descent = au::from_pt(self.ctfont.descent() as float);
let metrics = FontMetrics {
underline_size: au::from_pt(self.ctfont.underline_thickness() as float),
// TODO: 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 float),
leading: au::from_pt(self.ctfont.leading() as float),
x_height: au::from_pt(self.ctfont.x_height() as float),
em_size: ascent + descent,
ascent: ascent,
descent: descent,
max_advance: au::from_pt(bounding_rect.size.width as float)
};
debug!("Font metrics (@%f pt): %?", self.ctfont.pt_size() as float, metrics);
return metrics;
}
}

View file

@ -1,12 +0,0 @@
pub struct QuartzFontContextHandle {
ctx: (),
drop { }
}
pub impl QuartzFontContextHandle {
// this is a placeholder until NSFontManager or whatever is bound in here.
static pub fn new() -> QuartzFontContextHandle {
QuartzFontContextHandle { ctx: () }
}
}

View file

@ -1,64 +0,0 @@
extern mod core_foundation;
extern mod core_text;
use cf = core_foundation;
use cf::array::CFArray;
use ct = core_text;
use ct::font::{
CTFont,
debug_font_names,
debug_font_traits,
};
use ct::font_collection::CTFontCollection;
use ct::font_descriptor::{
CTFontDescriptor,
CTFontDescriptorRef,
debug_descriptor,
};
use gfx::font::FontHandle;
use gfx::font_list::FontEntry;
use font::{QuartzFontHandle};
use dvec::DVec;
use send_map::{linear, SendMap};
pub struct QuartzFontListHandle {
collection: CTFontCollection,
}
pub impl QuartzFontListHandle {
static pub fn new(_fctx: &native::FontContextHandle) -> QuartzFontListHandle {
QuartzFontListHandle { collection: CTFontCollection::new() }
}
fn get_available_families(&const self,fctx: &native::FontContextHandle) -> linear::LinearMap<~str, @FontFamily> {
// since we mutate it repeatedly, must be mut variable.
let mut family_map : linear::LinearMap<~str, @FontFamily> = linear::LinearMap();
let descriptors : CFArray<CTFontDescriptorRef, CTFontDescriptor>;
descriptors = self.collection.get_descriptors();
for descriptors.each |desc: &CTFontDescriptor| {
// TODO: for each descriptor, make a FontEntry.
let font = CTFont::new_from_descriptor(desc, 0.0);
let handle = result::unwrap(QuartzFontHandle::new_from_CTFont(fctx, move font));
let family_name = handle.family_name();
debug!("Looking for family name: %s", family_name);
let family = match family_map.find(&family_name) {
Some(fam) => fam,
None => {
debug!("Creating new FontFamily for family: %s", family_name);
let new_family = @FontFamily::new(family_name);
family_map.insert(move family_name, new_family);
new_family
}
};
debug!("Creating new FontEntry for face: %s", handle.face_name());
let entry = @FontEntry::new(family, move handle);
family.entries.push(entry);
// TODO: append FontEntry to hashtable value
}
return move family_map;
}
}

View file

@ -1,103 +0,0 @@
use compositor::LayerBuffer;
use gfx::au;
use gfx::{Au, Font, FontContext};
use image::base::Image;
use opts::Opts;
use text::TextRun;
use util::range::Range;
use azure::azure_hl::{AsAzureRect, B8G8R8A8, Color, ColorPattern, DrawOptions};
use azure::azure_hl::{DrawSurfaceOptions, DrawTarget, Linear, StrokeOptions};
use azure::{AzDrawOptions, AzFloat};
use core::dvec::DVec;
use core::libc::types::common::c99::uint16_t;
use core::ptr::to_unsafe_ptr;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use std::arc::ARC;
struct RenderContext {
canvas: &LayerBuffer,
font_ctx: @FontContext,
opts: &Opts
}
impl RenderContext {
pub fn get_draw_target(&self) -> &self/DrawTarget {
&self.canvas.draw_target
}
pub fn draw_solid_color(&self, bounds: &Rect<Au>, color: Color) {
self.canvas.draw_target.fill_rect(&bounds.to_azure_rect(), &ColorPattern(color));
}
pub fn draw_border(&self, bounds: &Rect<Au>, width: Au, color: Color) {
let pattern = ColorPattern(color);
let stroke_fields = 2; // CAP_SQUARE
let width_px = au::to_px(width);
let rect = if width_px % 2 == 0 {
bounds.to_azure_rect()
} else {
bounds.to_azure_snapped_rect()
};
let stroke_opts = StrokeOptions(width_px as AzFloat, 10 as AzFloat, stroke_fields);
let draw_opts = DrawOptions(1 as AzFloat, 0 as uint16_t);
self.canvas.draw_target.stroke_rect(&rect, &pattern, &stroke_opts, &draw_opts);
}
pub fn draw_image(&self, bounds: Rect<Au>, image: ARC<~Image>) {
let image = std::arc::get(&image);
let size = Size2D(image.width as i32, image.height as i32);
let stride = image.width * 4;
let draw_target_ref = &self.canvas.draw_target;
let azure_surface = draw_target_ref.create_source_surface_from_data(image.data, size,
stride as i32, B8G8R8A8);
let source_rect = Rect(Point2D(0 as AzFloat, 0 as AzFloat),
Size2D(image.width as AzFloat, image.height as AzFloat));
let dest_rect = bounds.to_azure_rect();
let draw_surface_options = DrawSurfaceOptions(Linear, true);
let draw_options = DrawOptions(1.0f as AzFloat, 0);
draw_target_ref.draw_surface(move azure_surface, dest_rect, source_rect,
draw_surface_options, draw_options);
}
fn clear(&self) {
let pattern = ColorPattern(Color(1f as AzFloat, 1f as AzFloat, 1f as AzFloat, 1f as AzFloat));
let rect = Rect(Point2D(self.canvas.rect.origin.x as AzFloat,
self.canvas.rect.origin.y as AzFloat),
Size2D(self.canvas.rect.size.width as AzFloat,
self.canvas.rect.size.height as AzFloat));
self.canvas.draw_target.fill_rect(&rect, &pattern);
}
}
trait to_float {
fn to_float() -> float;
}
impl u8 : to_float {
fn to_float() -> float {
(self as float) / 255f
}
}
trait ToAzureRect {
fn to_azure_rect() -> Rect<AzFloat>;
fn to_azure_snapped_rect() -> Rect<AzFloat>;
}
impl Rect<Au> : ToAzureRect {
fn to_azure_rect() -> Rect<AzFloat> {
Rect(Point2D(au::to_px(self.origin.x) as AzFloat, au::to_px(self.origin.y) as AzFloat),
Size2D(au::to_px(self.size.width) as AzFloat, au::to_px(self.size.height) as AzFloat))
}
fn to_azure_snapped_rect() -> Rect<AzFloat> {
Rect(Point2D(au::to_px(self.origin.x) as AzFloat + 0.5f as AzFloat, au::to_px(self.origin.y) as AzFloat + 0.5f as AzFloat),
Size2D(au::to_px(self.size.width) as AzFloat, au::to_px(self.size.height) as AzFloat))
}
}

View file

@ -1,137 +0,0 @@
use gfx::display_list::DisplayList;
use gfx::compositor::{LayerBuffer, LayerBufferSet};
use opts::Opts;
use util::time;
use azure::AzFloat;
use azure::azure_hl::{B8G8R8A8, DrawTarget};
use core::libc::c_int;
use core::pipes::Chan;
use geom::matrix2d::Matrix2D;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use std::arc::ARC;
use std::arc;
const TILE_SIZE: uint = 512;
pub struct RenderLayer {
display_list: DisplayList,
size: Size2D<uint>
}
type RenderFn = &fn(layer: *RenderLayer,
buffer: LayerBuffer,
return_buffer: Chan<LayerBuffer>);
/// Given a layer and a buffer, either reuses the buffer (if it's of the right size and format)
/// or creates a new buffer (if it's not of the appropriate size and format) and invokes the
/// given callback with the render layer and the buffer. Returns the resulting layer buffer (which
/// might be the old layer buffer if it had the appropriate size and format).
pub fn render_layers(layer_ref: *RenderLayer,
buffer_set: LayerBufferSet,
opts: &Opts,
f: RenderFn) -> LayerBufferSet {
let mut buffers = match move buffer_set { LayerBufferSet { buffers: move b } => move b };
// FIXME: Try not to create a new array here.
let new_buffer_ports = dvec::DVec();
// Divide up the layer into tiles.
do time::time("rendering: preparing buffers") {
let layer: &RenderLayer = unsafe { cast::transmute(layer_ref) };
let mut y = 0;
while y < layer.size.height {
let mut x = 0;
while x < layer.size.width {
// Figure out the dimension of this tile.
let right = uint::min(x + TILE_SIZE, layer.size.width);
let bottom = uint::min(y + TILE_SIZE, layer.size.height);
let width = right - x;
let height = bottom - y;
// Round the width up the nearest 32 pixels for DMA on the Mac.
let mut stride = width;
if stride % 32 != 0 {
stride = (stride & !(32 - 1)) + 32;
}
assert stride % 32 == 0;
assert stride >= width;
debug!("tile stride %u", stride);
let tile_rect = Rect(Point2D(x, y), Size2D(width, height));
let buffer;
// FIXME: Try harder to search for a matching tile.
// FIXME: Don't use shift; it's bad for perf. Maybe reverse and pop.
/*if buffers.len() != 0 && buffers[0].rect == tile_rect {
debug!("reusing tile, (%u, %u)", x, y);
buffer = buffers.shift();
} else {*/
// Create a new buffer.
debug!("creating tile, (%u, %u)", x, y);
let size = Size2D(stride as i32, height as i32);
let mut data: ~[u8] = ~[0];
let offset;
unsafe {
// FIXME: Evil black magic to ensure that we don't perform a slow memzero
// of this buffer. This should be made safe.
let align = 256;
let len = ((size.width * size.height * 4) as uint) + align;
vec::reserve(&mut data, len);
vec::raw::set_len(&mut data, len);
// Round up to the nearest 32-byte-aligned address for DMA on the Mac.
let addr: uint = cast::transmute(ptr::to_unsafe_ptr(&data[0]));
if addr % align == 0 {
offset = 0;
} else {
offset = align - addr % align;
}
debug!("tile offset is %u, expected addr is %x", offset, addr + offset);
}
buffer = LayerBuffer {
draw_target: DrawTarget::new_with_data(opts.render_backend,
move data,
offset,
size,
size.width * 4,
B8G8R8A8),
rect: tile_rect,
stride: stride
};
//}
// Create a port and channel pair to receive the new buffer.
let (new_buffer_chan, new_buffer_port) = pipes::stream();
// Send the buffer to the child.
f(layer_ref, move buffer, move new_buffer_chan);
// Enqueue the port.
new_buffer_ports.push(move new_buffer_port);
x += TILE_SIZE;
}
y += TILE_SIZE;
}
}
let new_buffers = dvec::DVec();
do time::time("rendering: waiting on subtasks") {
for new_buffer_ports.each |new_buffer_port| {
new_buffers.push(new_buffer_port.recv());
}
}
return LayerBufferSet { buffers: move dvec::unwrap(move new_buffers) };
}

View file

@ -1,159 +0,0 @@
use dl = display_list;
use gfx::{FontContext, RenderContext, RenderLayer};
use gfx::compositor::{Compositor, LayerBufferSet};
use gfx::render_layers;
use opts::Opts;
use platform::osmain;
use render_layers::render_layers;
use azure::AzFloat;
use core::comm::*;
use core::libc::size_t;
use core::libc::types::common::c99::uint16_t;
use core::pipes::{Port, Chan};
use core::task::SingleThreaded;
use geom::matrix2d::Matrix2D;
use std::arc::ARC;
use std::arc;
use std::cell::Cell;
use std::thread_pool::ThreadPool;
pub enum Msg {
RenderMsg(RenderLayer),
ExitMsg(pipes::Chan<()>)
}
pub type RenderTask = comm::Chan<Msg>;
pub fn RenderTask<C: Compositor Send>(compositor: C, opts: Opts) -> RenderTask {
let compositor_cell = Cell(move compositor);
let opts_cell = Cell(move opts);
do task::spawn_listener |po: comm::Port<Msg>, move compositor_cell, move opts_cell| {
let (layer_buffer_channel, layer_buffer_set_port) = pipes::stream();
let compositor = compositor_cell.take();
compositor.begin_drawing(move layer_buffer_channel);
// FIXME: Annoying three-cell dance here. We need one-shot closures.
let opts = opts_cell.with_ref(|o| copy *o);
let n_threads = opts.n_render_threads;
let new_opts_cell = Cell(move opts);
let thread_pool = do ThreadPool::new(n_threads, Some(SingleThreaded))
|move new_opts_cell| {
let opts_cell = Cell(new_opts_cell.with_ref(|o| copy *o));
let f: ~fn(uint) -> ThreadRenderContext = |thread_index, move opts_cell| {
ThreadRenderContext {
thread_index: thread_index,
font_ctx: @FontContext::new(opts_cell.with_ref(|o| o.render_backend), false),
opts: opts_cell.with_ref(|o| copy *o),
}
};
move f
};
Renderer {
port: po,
compositor: move compositor,
mut layer_buffer_set_port: Cell(move layer_buffer_set_port),
thread_pool: move thread_pool,
opts: opts_cell.take()
}.start();
}
}
/// Data that needs to be kept around for each render thread.
priv struct ThreadRenderContext {
thread_index: uint,
font_ctx: @FontContext,
opts: Opts,
}
priv struct Renderer<C: Compositor Send> {
port: comm::Port<Msg>,
compositor: C,
layer_buffer_set_port: Cell<pipes::Port<LayerBufferSet>>,
thread_pool: ThreadPool<ThreadRenderContext>,
opts: Opts,
}
impl<C: Compositor Send> Renderer<C> {
fn start() {
debug!("renderer: beginning rendering loop");
loop {
match self.port.recv() {
RenderMsg(move render_layer) => self.render(move render_layer),
ExitMsg(response_ch) => {
response_ch.send(());
break;
}
}
}
}
fn render(render_layer: RenderLayer) {
debug!("renderer: got render request");
let layer_buffer_set_port = self.layer_buffer_set_port.take();
if !layer_buffer_set_port.peek() {
warn!("renderer: waiting on layer buffer");
}
let layer_buffer_set = layer_buffer_set_port.recv();
let (layer_buffer_set_channel, new_layer_buffer_set_port) = pipes::stream();
self.layer_buffer_set_port.put_back(move new_layer_buffer_set_port);
let layer_buffer_set_cell = Cell(move layer_buffer_set);
let layer_buffer_set_channel_cell = Cell(move layer_buffer_set_channel);
#debug("renderer: rendering");
do util::time::time(~"rendering") {
let layer_buffer_set = layer_buffer_set_cell.take();
let layer_buffer_set_channel = layer_buffer_set_channel_cell.take();
let layer_buffer_set = do render_layers(ptr::to_unsafe_ptr(&render_layer),
move layer_buffer_set,
&self.opts)
|render_layer_ref, layer_buffer, buffer_chan| {
let layer_buffer_cell = Cell(move layer_buffer);
do self.thread_pool.execute |thread_render_context,
move render_layer_ref,
move buffer_chan,
move layer_buffer_cell| {
do layer_buffer_cell.with_ref |layer_buffer| {
// Build the render context.
let ctx = RenderContext {
canvas: layer_buffer,
font_ctx: thread_render_context.font_ctx,
opts: &thread_render_context.opts
};
// Apply the translation to render the tile we want.
let matrix: Matrix2D<AzFloat> = Matrix2D::identity();
let matrix = matrix.translate(&-(layer_buffer.rect.origin.x as AzFloat),
&-(layer_buffer.rect.origin.y as AzFloat));
layer_buffer.draw_target.set_transform(&matrix);
// Clear the buffer.
ctx.clear();
// Draw the display list.
let render_layer: &RenderLayer = unsafe {
cast::transmute(render_layer_ref)
};
render_layer.display_list.draw_into_context(&ctx);
}
// Send back the buffer.
buffer_chan.send(layer_buffer_cell.take());
}
};
#debug("renderer: returning surface");
self.compositor.draw(move layer_buffer_set_channel, move layer_buffer_set);
}
}
}

View file

@ -1,40 +0,0 @@
use geom::size::Size2D;
pub enum format {
fo_rgba_8888
// TODO: RGB 565, others?
}
impl format: cmp::Eq {
pure fn eq(other: &format) -> bool {
match (self, *other) {
(fo_rgba_8888, fo_rgba_8888) => true,
}
}
pure fn ne(other: &format) -> bool {
return !self.eq(other);
}
}
pub type image_surface = {
size: Size2D<int>,
format: format,
buffer: ~[u8]
};
impl format {
fn bpp() -> uint {
match self {
fo_rgba_8888 => 32u
}
}
}
pub fn image_surface(size: Size2D<int>, format: format) -> image_surface {
{
size: copy size,
format: format,
buffer: vec::from_elem((size.area() as uint) * format.bpp(), 0u8)
}
}

View file

@ -1,49 +0,0 @@
export Image;
export load;
export load_from_memory;
export test_image_bin;
use stb_image = stb_image::image;
// FIXME: Images must not be copied every frame. Instead we should atomically
// reference count them.
pub type Image = stb_image::Image;
pub fn Image(width: uint, height: uint, depth: uint, data: ~[u8]) -> Image {
stb_image::new_image(width, height, depth, move data)
}
const TEST_IMAGE: [u8 * 4962] = #include_bin("test.jpeg");
fn test_image_bin() -> ~[u8] {
return vec::from_fn(4962, |i| TEST_IMAGE[i]);
}
pub fn load_from_memory(buffer: &[u8]) -> Option<Image> {
// Can't remember why we do this. Maybe it's what cairo wants
const FORCE_DEPTH: uint = 4;
do stb_image::load_from_memory_with_depth(buffer, FORCE_DEPTH).map |image| {
assert image.depth == 4;
// Do color space conversion :(
let data = do vec::from_fn(image.width * image.height * 4) |i| {
let color = i % 4;
let pixel = i / 4;
match color {
0 => image.data[pixel * 4 + 2],
1 => image.data[pixel * 4 + 1],
2 => image.data[pixel * 4 + 0],
3 => 0xffu8,
_ => fail
}
};
assert image.data.len() == data.len();
Image(image.width, image.height, image.depth, move data)
}
}

View file

@ -1,24 +0,0 @@
use gfx::surface;
use io::WriterUtil;
fn encode(writer: io::Writer, surface: &surface::image_surface) {
assert surface.format == gfx::surface::fo_rgba_8888;
writer.write_u8(0u8); // identsize
writer.write_u8(0u8); // colourmaptype
writer.write_u8(2u8); // imagetype
writer.write_le_u16(0u16); // colourmapstart
writer.write_le_u16(0u16); // colourmaplength
writer.write_u8(16u8); // colourmapbits
writer.write_le_u16(0u16); // xstart
writer.write_le_u16(0u16); // ystart
writer.write_le_u16(surface.size.width as u16); // width
writer.write_le_u16(surface.size.height as u16); // height
writer.write_u8(32u8); // bits
writer.write_u8(0x30u8); // descriptor
writer.write(surface.buffer);
}

View file

@ -1,97 +0,0 @@
use core::util::replace;
use std::net::url::Url;
use std::arc::{ARC, clone, get};
use resource::image_cache_task::{ImageCacheTask, ImageReady, ImageNotReady, ImageFailed};
use mod resource::image_cache_task;
use resource::local_image_cache::LocalImageCache;
use geom::size::Size2D;
/** A struct to store image data. The image will be loaded once, the
first time it is requested, and an arc will be stored. Clones of
this arc are given out on demand.
*/
pub struct ImageHolder {
url : Url,
mut image : Option<ARC<~Image>>,
mut cached_size: Size2D<int>,
local_image_cache: @LocalImageCache,
}
fn ImageHolder(url : Url, local_image_cache: @LocalImageCache) -> ImageHolder {
debug!("ImageHolder() %?", url.to_str());
let holder = ImageHolder {
url : move url,
image : None,
cached_size : Size2D(0,0),
local_image_cache: local_image_cache,
};
// Tell the image cache we're going to be interested in this url
// FIXME: These two messages must be sent to prep an image for use
// but they are intended to be spread out in time. Ideally prefetch
// should be done as early as possible and decode only once we
// are sure that the image will be used.
local_image_cache.prefetch(&holder.url);
local_image_cache.decode(&holder.url);
move holder
}
impl ImageHolder {
/**
This version doesn't perform any computation, but may be stale w.r.t.
newly-available image data that determines size.
The intent is that the impure version is used during layout when
dimensions are used for computing layout.
*/
pure fn size() -> Size2D<int> {
self.cached_size
}
/** Query and update current image size */
fn get_size() -> Option<Size2D<int>> {
debug!("get_size() %?", self.url);
match self.get_image() {
Some(img) => {
let img_ref = get(&img);
self.cached_size = Size2D(img_ref.width as int,
img_ref.height as int);
Some(copy self.cached_size)
},
None => None
}
}
fn get_image() -> Option<ARC<~Image>> {
debug!("get_image() %?", self.url);
// If this is the first time we've called this function, load
// the image and store it for the future
if self.image.is_none() {
match self.local_image_cache.get_image(&self.url).recv() {
ImageReady(move image) => {
self.image = Some(move image);
}
ImageNotReady => {
debug!("image not ready for %s", self.url.to_str());
}
ImageFailed => {
debug!("image decoding failed for %s", self.url.to_str());
}
}
}
// Clone isn't pure so we have to swap out the mutable image option
let image = replace(&mut self.image, None);
let result = match image {
Some(ref image) => Some(clone(image)),
None => None
};
replace(&mut self.image, move image);
return move result;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -3,10 +3,11 @@ use newcss::values::*;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use gfx::display_list::{DisplayList, DisplayListBuilder};
use gfx::display_list::DisplayList;
use gfx::geometry::Au;
use layout::box::{RenderBox};
use layout::context::LayoutContext;
use layout::display_list_builder::DisplayListBuilder;
use layout::flow::{FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow};
use util::tree;

View file

@ -1,39 +1,34 @@
/* Fundamental layout structures and algorithms. */
use geom::{Rect, Size2D, Point2D};
use dom::element::{ElementKind, HTMLDivElement, HTMLImageElement};
use dom::node::{Element, Node, NodeData, NodeKind, NodeTree};
use gfx::{au, dl};
use gfx::{
Au,
DisplayItem,
DisplayList,
};
use image::{Image, ImageHolder};
use layout::context::LayoutContext;
use layout::debug::BoxedDebugMethods;
use layout::display_list_builder::DisplayListBuilder;
use layout::flow::FlowContext;
use layout::text::TextBoxData;
use newcss::color::{Color, rgba, rgb};
use newcss::complete::CompleteStyle;
use newcss::units::{BoxSizing, Length, Px};
use newcss::values::{CSSBorderColor, CSSPositionAbsolute};
use newcss::values::{CSSBorderWidthLength, CSSBorderWidthMedium};
use newcss::values::{CSSDisplay, Specified, CSSBackgroundColorColor, CSSBackgroundColorTransparent};
use servo_text::TextRun;
use util::range::*;
use util::tree;
use util::tree::ReadMethods;
use arc = std::arc;
use arc::ARC;
use core::dvec::DVec;
use core::to_str::ToStr;
use core::rand;
use core::task::spawn;
use geom::{Point2D, Rect, Size2D};
use gfx::display_list::{DisplayItem, DisplayList};
use gfx::geometry::Au;
use gfx::image::base::Image;
use gfx::image::holder::ImageHolder;
use gfx::text::text_run::TextRun;
use gfx::util::range::*;
use newcss::color::{Color, rgba, rgb};
use newcss::complete::CompleteStyle;
use newcss::units::{BoxSizing, Length, Px};
use newcss::values::{CSSBackgroundColorColor, CSSBackgroundColorTransparent, CSSBorderColor};
use newcss::values::{CSSBorderWidthLength, CSSBorderWidthMedium, CSSDisplay, CSSPositionAbsolute};
use newcss::values::{Specified};
use std::arc::ARC;
use std::net::url::Url;
use task::spawn;
/**
Render boxes (`struct RenderBox`) are the leafs of the layout
@ -131,7 +126,7 @@ fn RenderBoxData(node: Node, ctx: @FlowContext, id: int) -> RenderBoxData {
RenderBoxData {
node : node,
mut ctx : ctx,
mut position : au::zero_rect(),
mut position : Au::zero_rect(),
font_size: Px(0.0),
id : id
}
@ -187,7 +182,7 @@ impl RenderBox : RenderBoxMethods {
let mut pieces_processed_count : uint = 0;
let mut remaining_width : Au = max_width;
let left_range = MutableRange(data.range.begin(), 0);
let left_range = MutableRange::new(data.range.begin(), 0);
let mut right_range : Option<Range> = None;
debug!("split_to_width: splitting text box (strlen=%u, range=%?, avail_width=%?)",
data.run.text.len(), data.range, max_width);
@ -264,7 +259,7 @@ impl RenderBox : RenderBoxMethods {
GenericBox(*) => Au(0),
// TODO: consult CSS 'width', margin, border.
// TODO: If image isn't available, consult 'width'.
ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width),
ImageBox(_,i) => Au::from_px(i.get_size().get_default(Size2D(0,0)).width),
TextBox(_,d) => d.run.min_width_for_range(d.range),
UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here."
}
@ -278,7 +273,7 @@ impl RenderBox : RenderBoxMethods {
// FlowContext will combine the width of this element and
// that of its children to arrive at the context width.
GenericBox(*) => Au(0),
ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width),
ImageBox(_,i) => Au::from_px(i.get_size().get_default(Size2D(0,0)).width),
// a text box cannot span lines, so assume that this is an unsplit text box.
@ -293,7 +288,7 @@ impl RenderBox : RenderBoxMethods {
for d.run.glyphs.iter_glyphs_for_range(line_range) |_char_i, glyph| {
line_width += glyph.advance()
}
max_line_width = au::max(max_line_width, line_width);
max_line_width = Au::max(max_line_width, line_width);
}
max_line_width
@ -328,8 +323,8 @@ impl RenderBox : RenderBoxMethods {
let size = i.size();
Rect {
origin: copy self.d().position.origin,
size: Size2D(au::from_px(size.width),
au::from_px(size.height))
size: Size2D(Au::from_px(size.width),
Au::from_px(size.height))
}
},
GenericBox(*) => {
@ -421,7 +416,7 @@ impl RenderBox : RenderBoxMethods {
// debug frames for text box bounds
debug!("%?", {
list.append_item(~DisplayItem::new_Border(&abs_box_bounds,
au::from_px(1),
Au::from_px(1),
rgb(0, 0, 200).to_gfx_color()))
; ()});
},
@ -469,10 +464,10 @@ impl RenderBox : RenderBoxMethods {
CSSBorderWidthLength(Px(right)),
CSSBorderWidthLength(Px(bottom)),
CSSBorderWidthLength(Px(left))) => {
let top_au = au::from_frac_px(top);
let right_au = au::from_frac_px(right);
let bottom_au = au::from_frac_px(bottom);
let left_au = au::from_frac_px(left);
let top_au = Au::from_frac_px(top);
let right_au = Au::from_frac_px(right);
let bottom_au = Au::from_frac_px(bottom);
let left_au = Au::from_frac_px(left);
let all_widths_equal = [top_au, right_au, bottom_au].all(|a| *a == left_au);

View file

@ -1,20 +1,21 @@
/** Creates CSS boxes from a DOM. */
use au = gfx::geometry;
use core::dvec::DVec;
use newcss::values::{CSSDisplay, CSSDisplayBlock, CSSDisplayInline, CSSDisplayInlineBlock, CSSDisplayNone};
use newcss::values::{Inherit, Specified};
use dom::element::*;
use dom::node::{Comment, Doctype, Element, Text, Node, LayoutData};
use image::holder::ImageHolder;
use layout::box::*;
use layout::block::BlockFlowData;
use layout::context::LayoutContext;
use layout::flow::*;
use layout::inline::InlineFlowData;
use layout::root::RootFlowData;
use option::is_none;
use util::tree;
use core::dvec::DVec;
use gfx::image::holder::ImageHolder;
use gfx::util::range::MutableRange;
use newcss::values::{CSSDisplay, CSSDisplayBlock, CSSDisplayInline, CSSDisplayInlineBlock};
use newcss::values::{CSSDisplayNone, Inherit, Specified};
pub struct LayoutTreeBuilder {
mut root_flow: Option<@FlowContext>,
mut next_bid: int,
@ -150,7 +151,7 @@ impl BoxGenerator {
self.flow.inline().boxes.push(*spacer);
}
}
let node_range : MutableRange = MutableRange(self.range_stack.pop(), 0);
let node_range: MutableRange = MutableRange::new(self.range_stack.pop(), 0);
node_range.extend_to(self.flow.inline().boxes.len());
assert node_range.length() > 0;
@ -393,7 +394,7 @@ impl LayoutTreeBuilder {
// TODO: this could be written as a pattern guard, but it triggers
// an ICE (mozilla/rust issue #3601)
if d.image.is_some() {
let holder = ImageHolder({copy *d.image.get_ref()},
let holder = ImageHolder::new({copy *d.image.get_ref()},
layout_ctx.image_cache);
@ImageBox(RenderBoxData(node, ctx, self.next_box_id()), move holder)

View file

@ -1,9 +1,7 @@
use geom::rect::Rect;
use gfx::{
Au,
FontContext,
};
use resource::local_image_cache::LocalImageCache;
use gfx::font_context::FontContext;
use gfx::geometry::Au;
use gfx::resource::local_image_cache::LocalImageCache;
use std::net::url::Url;
/* Represents layout task context. */

View file

@ -11,7 +11,6 @@ use either::{Left, Right};
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use gfx::display_list::DisplayList;
use layout::box::{RenderBox, TextBox};
use layout::context::LayoutContext;
use layout::flow::FlowContext;
@ -19,6 +18,8 @@ use layout::text::TextBoxData;
use util::tree;
use vec::push;
use gfx::display_list::DisplayList;
/** A builder object that manages display list builder should mainly
hold information about the initial request and desired result---for
example, whether the DisplayList to be used for painting or hit

View file

@ -1,20 +1,20 @@
use au = gfx::geometry;
use au::Au;
use core::dvec::DVec;
use dom::node::Node;
use geom::rect::Rect;
use geom::point::Point2D;
use gfx::display_list::{DisplayList, DisplayListBuilder};
// TODO: pub-use these
use layout::block::BlockFlowData;
use layout::box::RenderBox;
use layout::context::LayoutContext;
use layout::debug::BoxedDebugMethods;
use layout::display_list_builder::DisplayListBuilder;
use layout::inline::{InlineFlowData, NodeRange};
use layout::root::RootFlowData;
use util::range::{Range, MutableRange};
use util::tree;
use core::dvec::DVec;
use geom::rect::Rect;
use geom::point::Point2D;
use gfx::display_list::DisplayList;
use gfx::geometry::Au;
use gfx::util::range::{Range, MutableRange};
/** Servo's experimental layout system builds a tree of FlowContexts
and RenderBoxes, and figures out positions and display attributes of
tree nodes. Positions are computed in several tree traversals driven
@ -103,7 +103,7 @@ fn FlowData(id: int) -> FlowData {
min_width: Au(0),
pref_width: Au(0),
position: au::zero_rect()
position: Au::zero_rect()
}
}

View file

@ -1,24 +1,19 @@
use au = gfx::geometry;
use geom::{Point2D, Rect, Size2D};
use dom::node::Node;
use gfx::dl;
use gfx::{
Au,
FontStyle,
};
use layout::box::*;
use layout::context::LayoutContext;
use layout::flow::{FlowContext, InlineFlow};
use layout::text::TextBoxData;
use newcss::values::{BoxAuto, BoxLength, Px};
use servo_text::util::*;
use util::range::{MutableRange, Range};
use util::tree;
use core::dlist::DList;
use core::dvec::DVec;
use num::Num;
use core::num::Num;
use geom::{Point2D, Rect, Size2D};
use gfx::font::FontStyle;
use gfx::geometry::Au;
use gfx::text::util::*;
use gfx::util::range::{MutableRange, Range};
use newcss::values::{BoxAuto, BoxLength, Px};
use std::arc;
/*
@ -158,7 +153,7 @@ struct TextRunScanner {
fn TextRunScanner(flow: @FlowContext) -> TextRunScanner {
TextRunScanner {
clump: util::range::empty_mut(),
clump: MutableRange::empty(),
flow: flow,
}
}
@ -333,7 +328,7 @@ fn LineboxScanner(inline: @FlowContext) -> LineboxScanner {
flow: inline,
new_boxes: DVec(),
work_list: DList(),
pending_line: {range: util::range::empty_mut(), mut width: Au(0)},
pending_line: {range: MutableRange::empty(), mut width: Au(0)},
line_spans: DVec()
}
}
@ -567,8 +562,8 @@ impl FlowContext : InlineLayout {
for self.inline().boxes.each |box| {
debug!("FlowContext[%d]: measuring %s", self.d().id, box.debug_str());
min_width = au::max(min_width, box.get_min_width(ctx));
pref_width = au::max(pref_width, box.get_pref_width(ctx));
min_width = Au::max(min_width, box.get_min_width(ctx));
pref_width = Au::max(pref_width, box.get_pref_width(ctx));
}
self.d().min_width = min_width;
@ -586,11 +581,11 @@ impl FlowContext : InlineLayout {
// over the box list, and/or put into RenderBox.
for self.inline().boxes.each |box| {
box.d().position.size.width = match *box {
@ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).width),
@ImageBox(_,img) => Au::from_px(img.get_size().get_default(Size2D(0,0)).width),
@TextBox(*) => { /* text boxes are initialized with dimensions */
box.d().position.size.width
},
@GenericBox(*) => au::from_px(45), /* TODO: should use CSS 'width'? */
@GenericBox(*) => Au::from_px(45), /* TODO: should use CSS 'width'? */
_ => fail fmt!("Tried to assign width to unknown Box variant: %?", box)
};
} // for boxes.each |box|
@ -609,24 +604,24 @@ impl FlowContext : InlineLayout {
fn assign_height_inline(@self, _ctx: &LayoutContext) {
// TODO: get from CSS 'line-height' property
let line_height = au::from_px(20);
let line_height = Au::from_px(20);
let mut cur_y = Au(0);
for self.inline().lines.eachi |i, line_span| {
debug!("assign_height_inline: processing line %u with box span: %?", i, line_span);
// coords relative to left baseline
let mut linebox_bounding_box = au::zero_rect();
let mut linebox_bounding_box = Au::zero_rect();
let boxes = &self.inline().boxes;
for line_span.eachi |box_i| {
let cur_box = boxes[box_i];
// compute box height.
cur_box.d().position.size.height = match cur_box {
@ImageBox(_,img) => au::from_px(img.size().height),
@ImageBox(_,img) => Au::from_px(img.size().height),
@TextBox(*) => { /* text boxes are initialized with dimensions */
cur_box.d().position.size.height
},
@GenericBox(*) => au::from_px(30), /* TODO: should use CSS 'height'? */
@GenericBox(*) => Au::from_px(30), /* TODO: should use CSS 'height'? */
_ => fail fmt!("Tried to assign height to unknown Box variant: %s", cur_box.debug_str())
};
@ -654,7 +649,7 @@ impl FlowContext : InlineLayout {
debug!("assign_height_inline: linebox bounding box = %?", linebox_bounding_box);
}
let linebox_height = linebox_bounding_box.size.height;
cur_y += au::max(line_height, linebox_height);
cur_y += Au::max(line_height, linebox_height);
} // /lines.each |line_span|
self.d().position.size.height = cur_y;

View file

@ -7,21 +7,16 @@ use content::content_task;
use css::select::new_css_select_ctx;
use dom::event::{Event, ReflowEvent};
use dom::node::{Node, LayoutData};
use gfx::render_task;
use gfx::{Au, DisplayList, FontContext, RenderLayer};
use gfx::{au, dl};
use layout::box::RenderBox;
use layout::box_builder::LayoutTreeBuilder;
use layout::context::LayoutContext;
use layout::display_list_builder::DisplayListBuilder;
use layout::traverse::*;
use opt = core::option;
use opts::Opts;
use render_task::RenderTask;
use resource::image_cache_task::{ImageCacheTask, ImageResponseMsg};
use resource::local_image_cache::LocalImageCache;
use util::time::time;
use core::comm::*;
use core::comm::*; // FIXME: Bad! Pipe-ify me.
use core::dvec::DVec;
use core::mutable::Mut;
use core::task::*;
@ -29,9 +24,16 @@ use core::util::replace;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use gfx::display_list::DisplayList;
use gfx::font_context::FontContext;
use gfx::geometry::Au;
use gfx::opts::Opts;
use gfx::render_layers::RenderLayer;
use gfx::render_task::{RenderMsg, RenderTask};
use newcss::select::SelectCtx;
use newcss::stylesheet::Stylesheet;
use newcss::types::OriginAuthor;
use opt = core::option;
use std::arc::ARC;
use std::cell::Cell;
use std::net::url::Url;
@ -159,8 +161,8 @@ impl Layout {
// Reset the image cache
self.local_image_cache.next_round(self.make_on_image_available_cb(move dom_event_chan));
let screen_size = Size2D(au::from_px(data.window_size.width as int),
au::from_px(data.window_size.height as int));
let screen_size = Size2D(Au::from_px(data.window_size.width as int),
Au::from_px(data.window_size.height as int));
let layout_ctx = LayoutContext {
image_cache: self.local_image_cache,
@ -202,20 +204,20 @@ impl Layout {
}
do time("layout: display list building") {
let builder = dl::DisplayListBuilder {
let builder = DisplayListBuilder {
ctx: &layout_ctx,
};
let mut render_layer = RenderLayer {
display_list: DisplayList::new(),
size: Size2D(au::to_px(screen_size.width) as uint,
au::to_px(screen_size.height) as uint)
size: Size2D(screen_size.width.to_px() as uint,
screen_size.height.to_px() as uint)
};
// TODO: set options on the builder before building
// TODO: be smarter about what needs painting
layout_root.build_display_list(&builder, &copy layout_root.d().position,
&mut render_layer.display_list);
self.render_task.send(render_task::RenderMsg(move render_layer));
self.render_task.send(RenderMsg(move render_layer));
} // time(layout: display list building)
// Tell content we're done
@ -242,8 +244,8 @@ impl Layout {
match rect {
None => Err(()),
Some(rect) => {
let size = Size2D(au::to_px(rect.size.width),
au::to_px(rect.size.height));
let size = Size2D(rect.size.width.to_px(),
rect.size.height.to_px());
Ok(ContentSize(move size))
}
}

View file

@ -2,11 +2,12 @@ use au = gfx::geometry;
use newcss::values::*;
use geom::point::Point2D;
use geom::rect::Rect;
use gfx::display_list::{DisplayList, DisplayListBuilder};
use gfx::display_list::DisplayList;
use gfx::geometry::Au;
use layout::box::RenderBox;
use layout::context::LayoutContext;
use layout::flow::{FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow};
use layout::display_list_builder::DisplayListBuilder;
use util::tree;
struct RootFlowData {

View file

@ -1,8 +1,9 @@
/** Text layout. */
use servo_text::TextRun;
use layout::box::{TextBox, RenderBox, RenderBoxData, UnscannedTextBox};
use util::range::Range;
use gfx::text::text_run::TextRun;
use gfx::util::range::Range;
pub struct TextBoxData {
run: @TextRun,

View file

@ -1,77 +0,0 @@
//! Configuration options for a single run of the servo application. Created
//! from command line arguments.
use azure::azure_hl::{BackendType, CairoBackend, CoreGraphicsBackend};
use azure::azure_hl::{CoreGraphicsAcceleratedBackend, Direct2DBackend, SkiaBackend};
pub struct Opts {
urls: ~[~str],
render_mode: RenderMode,
render_backend: BackendType,
n_render_threads: uint,
}
pub enum RenderMode {
Screen,
Png(~str)
}
#[allow(non_implicitly_copyable_typarams)]
pub fn from_cmdline_args(args: &[~str]) -> Opts {
use std::getopts;
let args = args.tail();
let opts = ~[
getopts::optopt(~"o"),
getopts::optopt(~"r"),
getopts::optopt(~"t"),
];
let opt_match = match getopts::getopts(args, opts) {
result::Ok(m) => { copy m }
result::Err(f) => { fail getopts::fail_str(copy f) }
};
let urls = if opt_match.free.is_empty() {
fail ~"servo asks that you provide 1 or more URLs"
} else {
copy opt_match.free
};
let render_mode = match getopts::opt_maybe_str(copy opt_match, ~"o") {
Some(move output_file) => { Png(move output_file) }
None => { Screen }
};
let render_backend = match getopts::opt_maybe_str(copy opt_match, ~"r") {
Some(move backend_str) => {
if backend_str == ~"direct2d" {
Direct2DBackend
} else if backend_str == ~"core-graphics" {
CoreGraphicsBackend
} else if backend_str == ~"core-graphics-accelerated" {
CoreGraphicsAcceleratedBackend
} else if backend_str == ~"cairo" {
CairoBackend
} else if backend_str == ~"skia" {
SkiaBackend
} else {
fail ~"unknown backend type"
}
}
None => CairoBackend
};
let n_render_threads: uint = match getopts::opt_maybe_str(move opt_match, ~"t") {
Some(move n_render_threads_str) => from_str::from_str(n_render_threads_str).get(),
None => 1, // FIXME: Number of cores.
};
Opts {
urls: move urls,
render_mode: move render_mode,
render_backend: move render_backend,
n_render_threads: n_render_threads,
}
}

View file

@ -1,10 +1,6 @@
use ShareGlContext = sharegl::platform::Context;
use dom::event::{Event, ResizeEvent};
use gfx::compositor::{Compositor, LayerBuffer, LayerBufferSet};
use layers::ImageLayer;
use opts::Opts;
use resize_rate_limiter::ResizeRateLimiter;
use util::time;
use azure::azure_hl::{BackendType, B8G8R8A8, DataSourceSurface, DrawTarget, SourceSurfaceMethods};
use core::dvec::DVec;
@ -15,10 +11,16 @@ use geom::matrix::{Matrix4, identity};
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use gfx::compositor::{Compositor, LayerBuffer, LayerBufferSet};
use gfx::opts::Opts;
use gfx::util::time;
use layers::ImageLayer;
use std::cell::Cell;
use std::cmp::FuzzyEq;
pub type OSMain = comm::Chan<Msg>;
pub struct OSMain {
chan: comm::Chan<Msg>
}
// FIXME: Move me over to opts.rs.
enum Mode {
@ -40,7 +42,8 @@ pub enum Msg {
fn OSMain(dom_event_chan: pipes::SharedChan<Event>, opts: Opts) -> OSMain {
let dom_event_chan = Cell(move dom_event_chan);
do on_osmain::<Msg> |po, move dom_event_chan, move opts| {
OSMain {
chan: do on_osmain::<Msg> |po, move dom_event_chan, move opts| {
do platform::runmain {
#debug("preparing to enter main loop");
@ -54,6 +57,7 @@ fn OSMain(dom_event_chan: pipes::SharedChan<Event>, opts: Opts) -> OSMain {
mainloop(mode, po, dom_event_chan.take(), &opts);
}
}
}
}
/// Azure surface wrapping to work with the layers infrastructure.
@ -258,10 +262,10 @@ compositor for the renderer
*/
impl OSMain : Compositor {
fn begin_drawing(next_dt: pipes::Chan<LayerBufferSet>) {
self.send(BeginDrawing(move next_dt))
self.chan.send(BeginDrawing(move next_dt))
}
fn draw(next_dt: pipes::Chan<LayerBufferSet>, draw_me: LayerBufferSet) {
self.send(Draw(move next_dt, move draw_me))
self.chan.send(Draw(move next_dt, move draw_me))
}
}

View file

@ -1,29 +0,0 @@
export factory;
use comm::Chan;
use task::spawn;
use resource_task::{ProgressMsg, Payload, Done};
use std::net::url::Url;
use io::{file_reader, ReaderUtil};
const READ_SIZE: uint = 1024;
pub fn factory(url: Url, progress_chan: Chan<ProgressMsg>) {
assert url.scheme == ~"file";
do spawn |move url| {
// FIXME: Resolve bug prevents us from moving the path out of the URL.
match file_reader(&Path(url.path)) {
Ok(reader) => {
while !reader.eof() {
let data = reader.read_bytes(READ_SIZE);
progress_chan.send(Payload(move data));
}
progress_chan.send(Done(Ok(())));
}
Err(*) => {
progress_chan.send(Done(Err(())));
}
};
}
}

View file

@ -1,38 +0,0 @@
export factory;
use comm::Chan;
use task::spawn;
use resource_task::{ProgressMsg, Payload, Done};
use std::net::url::Url;
use http_client::{uv_http_request};
pub fn factory(url: Url, progress_chan: Chan<ProgressMsg>) {
assert url.scheme == ~"http";
do spawn |move url| {
#debug("http_loader: requesting via http: %?", copy url);
let request = uv_http_request(copy url);
let errored = @mut false;
do request.begin |event, copy url| {
let url = copy url;
match event {
http_client::Status(*) => { }
http_client::Payload(data) => {
#debug("http_loader: got data from %?", url);
let mut junk = None;
*data <-> junk;
progress_chan.send(Payload(option::unwrap(move junk)));
}
http_client::Error(*) => {
#debug("http_loader: error loading %?", url);
*errored = true;
progress_chan.send(Done(Err(())));
}
}
}
if !*errored {
progress_chan.send(Done(Ok(())));
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,150 +0,0 @@
/*!
An adapter for ImageCacheTask that does local caching to avoid
extra message traffic, it also avoids waiting on the same image
multiple times and thus triggering reflows multiple times.
*/
use clone_arc = std::arc::clone;
use std::net::url::Url;
use pipes::{Port, Chan, stream};
use image_cache_task::{ImageCacheTask, ImageResponseMsg, Prefetch, Decode, GetImage, WaitForImage, ImageReady, ImageNotReady, ImageFailed};
use util::url::{UrlMap, url_map};
pub fn LocalImageCache(image_cache_task: ImageCacheTask) -> LocalImageCache {
LocalImageCache {
image_cache_task: move image_cache_task,
round_number: 1,
mut on_image_available: None,
state_map: url_map()
}
}
pub struct LocalImageCache {
priv image_cache_task: ImageCacheTask,
priv mut round_number: uint,
priv mut on_image_available: Option<@fn() -> ~fn(ImageResponseMsg)>,
priv state_map: UrlMap<@ImageState>
}
priv struct ImageState {
mut prefetched: bool,
mut decoded: bool,
mut last_request_round: uint,
mut last_response: ImageResponseMsg
}
#[allow(non_implicitly_copyable_typarams)] // Using maps of Urls
pub impl LocalImageCache {
/// The local cache will only do a single remote request for a given
/// URL in each 'round'. Layout should call this each time it begins
// FIXME: 'pub' is an unexpected token?
/* pub */ fn next_round(on_image_available: @fn() -> ~fn(ImageResponseMsg)) {
self.round_number += 1;
self.on_image_available = Some(move on_image_available);
}
pub fn prefetch(url: &Url) {
let state = self.get_state(url);
if !state.prefetched {
self.image_cache_task.send(Prefetch(copy *url));
state.prefetched = true;
}
}
pub fn decode(url: &Url) {
let state = self.get_state(url);
if !state.decoded {
self.image_cache_task.send(Decode(copy *url));
state.decoded = true;
}
}
// FIXME: Should return a Future
pub fn get_image(url: &Url) -> Port<ImageResponseMsg> {
let state = self.get_state(url);
// Save the previous round number for comparison
let last_round = state.last_request_round;
// Set the current round number for this image
state.last_request_round = self.round_number;
match state.last_response {
ImageReady(ref image) => {
// FIXME: appease borrowck
unsafe {
let (chan, port) = pipes::stream();
chan.send(ImageReady(clone_arc(image)));
return move port;
}
}
ImageNotReady => {
if last_round == self.round_number {
let (chan, port) = pipes::stream();
chan.send(ImageNotReady);
return move port;
} else {
// We haven't requested the image from the
// remote cache this round
}
}
ImageFailed => {
let (chan, port) = pipes::stream();
chan.send(ImageFailed);
return move port;
}
}
let (response_chan, response_port) = pipes::stream();
self.image_cache_task.send(GetImage(copy *url, move response_chan));
let response = response_port.recv();
match response {
ImageNotReady => {
// Need to reflow when the image is available
// FIXME: Instead we should be just passing a Future
// to the caller, then to the display list. Finally,
// the compositor should be resonsible for waiting
// on the image to load and triggering layout
let image_cache_task = self.image_cache_task.clone();
assert self.on_image_available.is_some();
let on_image_available = self.on_image_available.get()();
let url = copy *url;
do task::spawn |move url, move on_image_available, move image_cache_task| {
let (response_chan, response_port) = pipes::stream();
image_cache_task.send(WaitForImage(copy url, move response_chan));
on_image_available(response_port.recv());
}
}
_ => ()
}
// Put a copy of the response in the cache
let response_copy = match response {
ImageReady(ref image) => ImageReady(clone_arc(image)),
ImageNotReady => ImageNotReady,
ImageFailed => ImageFailed
};
state.last_response = move response_copy;
let (chan, port) = pipes::stream();
chan.send(move response);
return move port;
}
priv fn get_state(url: &Url) -> @ImageState {
match self.state_map.find(copy *url) {
Some(state) => state,
None => {
let new_state = @ImageState {
prefetched: false,
decoded: false,
last_request_round: 0,
last_response: ImageNotReady
};
self.state_map.insert(copy *url, new_state);
self.get_state(url)
}
}
}
}

View file

@ -1,157 +0,0 @@
/*!
A task that takes a URL and streams back the binary data
*/
use comm::{Chan, Port};
use task::{spawn, spawn_listener};
use std::net::url;
use std::net::url::{Url, to_str};
pub enum ControlMsg {
/// Request the data associated with a particular URL
Load(Url, Chan<ProgressMsg>),
Exit
}
/// Messages sent in response to a `Load` message
pub enum ProgressMsg {
/// Binary data - there may be multiple of these
Payload(~[u8]),
/// Indicates loading is complete, either successfully or not
Done(Result<(), ()>)
}
impl ProgressMsg: cmp::Eq {
pure fn eq(other: &ProgressMsg) -> bool {
match (copy self, copy *other) {
(Payload(a), Payload(b)) => a == b,
(Done(a), Done(b)) => a == b,
(Payload(*), _)
| (Done(*), _) => false
}
}
pure fn ne(other: &ProgressMsg) -> bool {
return !self.eq(other);
}
}
/// Handle to a resource task
type ResourceTask = Chan<ControlMsg>;
/**
Creates a task to load a specific resource
The ResourceManager delegates loading to a different type of loader task for
each URL scheme
*/
type LoaderTaskFactory = fn~(url: Url, Chan<ProgressMsg>);
/// Create a ResourceTask with the default loaders
fn ResourceTask() -> ResourceTask {
let loaders = ~[
(~"file", file_loader::factory),
(~"http", http_loader::factory)
];
create_resource_task_with_loaders(move loaders)
}
fn create_resource_task_with_loaders(loaders: ~[(~str, LoaderTaskFactory)]) -> ResourceTask {
do spawn_listener |from_client, move loaders| {
// TODO: change copy to move once we can move out of closures
ResourceManager(from_client, copy loaders).start()
}
}
pub struct ResourceManager {
from_client: Port<ControlMsg>,
/// Per-scheme resource loaders
loaders: ~[(~str, LoaderTaskFactory)],
}
pub fn ResourceManager(from_client: Port<ControlMsg>,
loaders: ~[(~str, LoaderTaskFactory)]) -> ResourceManager {
ResourceManager {
from_client : move from_client,
loaders : move loaders,
}
}
impl ResourceManager {
fn start() {
loop {
match self.from_client.recv() {
Load(url, progress_chan) => {
self.load(copy url, progress_chan)
}
Exit => {
break
}
}
}
}
fn load(url: Url, progress_chan: Chan<ProgressMsg>) {
match self.get_loader_factory(&url) {
Some(loader_factory) => {
#debug("resource_task: loading url: %s", to_str(copy url));
loader_factory(move url, progress_chan);
}
None => {
#debug("resource_task: no loader for scheme %s", url.scheme);
progress_chan.send(Done(Err(())));
}
}
}
fn get_loader_factory(url: &Url) -> Option<LoaderTaskFactory> {
for self.loaders.each |scheme_loader| {
let (scheme, loader_factory) = copy *scheme_loader;
if scheme == url.scheme {
return Some(move loader_factory);
}
}
return None;
}
}
#[test]
fn test_exit() {
let resource_task = ResourceTask();
resource_task.send(Exit);
}
#[test]
#[allow(non_implicitly_copyable_typarams)]
fn test_bad_scheme() {
let resource_task = ResourceTask();
let progress = Port();
resource_task.send(Load(url::from_str(~"bogus://whatever").get(), progress.chan()));
match progress.recv() {
Done(result) => { assert result.is_err() }
_ => fail
}
resource_task.send(Exit);
}
#[test]
#[allow(non_implicitly_copyable_typarams)]
fn should_delegate_to_scheme_loader() {
let payload = ~[1, 2, 3];
let loader_factory = fn~(_url: Url, progress_chan: Chan<ProgressMsg>, copy payload) {
progress_chan.send(Payload(copy payload));
progress_chan.send(Done(Ok(())));
};
let loader_factories = ~[(~"snicklefritz", move loader_factory)];
let resource_task = create_resource_task_with_loaders(move loader_factories);
let progress = Port();
resource_task.send(Load(url::from_str(~"snicklefritz://heya").get(), progress.chan()));
assert progress.recv() == Payload(move payload);
assert progress.recv() == Done(Ok(()));
resource_task.send(Exit);
}

View file

@ -10,6 +10,7 @@
extern mod azure;
extern mod cairo;
extern mod geom;
extern mod gfx (name = "servo_gfx");
extern mod glut;
extern mod http_client;
extern mod hubbub;
@ -69,54 +70,6 @@ pub mod layout {
mod aux;
}
pub mod gfx {
priv mod render_context;
// rendering
pub mod color;
pub mod compositor;
pub mod display_list;
pub mod geometry;
pub mod render_layers;
pub mod render_task;
pub mod surface;
// fonts
pub mod font;
pub mod font_context;
pub mod font_list;
// Pub-uses for multiple implementations. Platform selection happens in
// font.rs, font_list.rs, font_context.rs
pub mod native;
#[cfg(target_os = "macos")]
pub mod quartz {
pub mod font;
pub mod font_context;
pub mod font_list;
}
#[cfg(target_os = "linux")]
pub mod freetype {
pub mod font;
pub mod font_context;
}
#[cfg(target_os = "linux")]
pub mod fontconfig {
pub mod font_list;
}
}
pub mod image {
pub mod base;
pub mod encode {
pub mod tga;
}
pub mod holder;
}
pub mod html {
pub mod cssparse;
pub mod hubbub_html_parser;
@ -128,37 +81,9 @@ pub mod platform {
priv mod resize_rate_limiter;
}
pub mod resource {
pub mod file_loader;
pub mod http_loader;
pub mod image_cache_task;
pub mod local_image_cache;
pub mod resource_task;
}
pub mod text {
pub mod glyph;
pub mod text_run;
pub mod util;
pub mod shaper;
// Below are the actual platform-specific parts.
pub mod harfbuzz {
pub mod shaper;
}
}
pub mod util {
pub mod actor;
pub mod cache;
pub mod range;
pub mod time;
pub mod tree;
pub mod url;
pub mod vec;
}
pub mod opts;
use servo_util = util;
use servo_text = text;

View file

@ -3,22 +3,23 @@ extern mod core_graphics;
#[cfg(target_os="macos")]
extern mod core_text;
use comm::*;
use option::swap_unwrap;
use platform::osmain;
use osmain::{OSMain, AddKeyHandler};
use opts::{Opts, Screen, Png};
use engine::{Engine, ExitMsg, LoadURLMsg};
use resource::image_cache_task::ImageCacheTask;
use resource::resource_task::ResourceTask;
use engine::{Engine, ExitMsg, LoadURLMsg}; // FIXME: "ExitMsg" is pollution.
use platform::osmain::{AddKeyHandler, OSMain};
use util::url::make_url;
use core::comm::*; // FIXME: Bad!
use core::option::swap_unwrap;
use core::pipes::{Port, Chan};
use pipes::{Port, Chan};
pub use gfx::opts::{Opts, Png, Screen}; // FIXME: Do we really want "Screen" and "Png" visible?
pub use gfx::resource;
pub use gfx::resource::image_cache_task::ImageCacheTask;
pub use gfx::resource::resource_task::ResourceTask;
pub use gfx::text;
pub use gfx::util::url::make_url;
fn main() {
let args = os::args();
run(&opts::from_cmdline_args(args))
run(&gfx::opts::from_cmdline_args(args))
}
#[allow(non_implicitly_copyable_typarams)]
@ -44,7 +45,7 @@ fn run_pipeline_screen(opts: &Opts) {
// Send each file to render then wait for keypress
let (keypress_to_engine, keypress_from_osmain) = pipes::stream();
osmain.send(AddKeyHandler(move keypress_to_engine));
osmain.chan.send(AddKeyHandler(move keypress_to_engine));
// Create a servo instance
let resource_task = ResourceTask();
@ -70,7 +71,7 @@ fn run_pipeline_screen(opts: &Opts) {
engine_task.send(engine::ExitMsg(move exit_chan));
exit_response_from_engine.recv();
osmain.send(osmain::Exit);
osmain.chan.send(platform::osmain::Exit);
}
fn run_pipeline_png(_opts: &Opts, _outfile: &str) {

View file

@ -1,9 +0,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 shaper::Shaper;
pub use text_run::TextRun;
pub use text_run::SendableTextRun;

View file

@ -1,619 +0,0 @@
use au = gfx::geometry;
use au::Au;
use core::cmp::{Ord, Eq};
use core::dvec::DVec;
use core::u16;
use geom::point::Point2D;
use num::from_int;
use std::sort;
use servo_util::range::Range;
use servo_util::vec::*;
// 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.
struct GlyphEntry {
value : u32
}
pure fn GlyphEntry(value: u32) -> GlyphEntry { GlyphEntry { value: value } }
/// The index of a particular glyph within a font
type GlyphIndex = u32;
// TODO: unify with bit flags?
enum BreakType {
BreakTypeNone,
BreakTypeNormal,
BreakTypeHyphen
}
const BREAK_TYPE_NONE : u8 = 0x0u8;
const BREAK_TYPE_NORMAL : u8 = 0x1u8;
const BREAK_TYPE_HYPHEN : u8 = 0x2u8;
pure fn break_flag_to_enum(flag: u8) -> BreakType {
if (flag & BREAK_TYPE_NONE) as bool { return BreakTypeNone; }
if (flag & BREAK_TYPE_NORMAL) as bool { return BreakTypeNormal; }
if (flag & BREAK_TYPE_HYPHEN) as bool { return BreakTypeHyphen; }
fail ~"Unknown break setting"
}
pure 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.
const FLAG_CHAR_IS_SPACE : u32 = 0x10000000u32;
// These two bits store some BREAK_TYPE_* flags
const FLAG_CAN_BREAK_MASK : u32 = 0x60000000u32;
const FLAG_CAN_BREAK_SHIFT : u32 = 29;
const FLAG_IS_SIMPLE_GLYPH : u32 = 0x80000000u32;
// glyph advance; in Au's.
const GLYPH_ADVANCE_MASK : u32 = 0x0FFF0000u32;
const GLYPH_ADVANCE_SHIFT : u32 = 16;
const GLYPH_ID_MASK : u32 = 0x0000FFFFu32;
// 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.
const GLYPH_COUNT_MASK : u32 = 0x00FFFF00u32;
const 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.
const FLAG_NOT_MISSING : u32 = 0x00000001u32;
const FLAG_NOT_CLUSTER_START : u32 = 0x00000002u32;
const FLAG_NOT_LIGATURE_GROUP_START : u32 = 0x00000004u32;
const FLAG_CHAR_IS_TAB : u32 = 0x00000008u32;
const FLAG_CHAR_IS_NEWLINE : u32 = 0x00000010u32;
const FLAG_CHAR_IS_LOW_SURROGATE : u32 = 0x00000020u32;
const CHAR_IDENTITY_FLAGS_MASK : u32 = 0x00000038u32;
pure fn is_simple_glyph_id(glyphId: GlyphIndex) -> bool {
((glyphId as u32) & GLYPH_ID_MASK) == glyphId
}
pure fn is_simple_advance(advance: Au) -> bool {
let unsignedAu = advance.to_int() as u32;
(unsignedAu & (GLYPH_ADVANCE_MASK >> GLYPH_ADVANCE_SHIFT)) == unsignedAu
}
type DetailedGlyphCount = u16;
pure fn InitialGlyphEntry() -> GlyphEntry {
GlyphEntry { value: 0 }
}
// Creates a GlyphEntry for the common case
pure fn SimpleGlyphEntry(index: GlyphIndex, advance: Au) -> GlyphEntry {
assert is_simple_glyph_id(index);
assert is_simple_advance(advance);
let index_mask = index as u32;
let advance_mask = (*advance as u32) << GLYPH_ADVANCE_SHIFT;
GlyphEntry {
value: index_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
pure fn ComplexGlyphEntry(startsCluster: bool, startsLigature: bool, glyphCount: uint) -> GlyphEntry {
assert glyphCount <= u16::max_value as uint;
let mut val = FLAG_NOT_MISSING;
if !startsCluster {
val |= FLAG_NOT_CLUSTER_START;
}
if !startsLigature {
val |= FLAG_NOT_LIGATURE_GROUP_START;
}
val |= (glyphCount as u32) << GLYPH_COUNT_SHIFT;
GlyphEntry {
value: val
}
}
// Create a GlyphEntry for the case where glyphs couldn't be found
// for the specified character.
pure fn MissingGlyphsEntry(glyphCount: uint) -> GlyphEntry {
assert glyphCount <= u16::max_value as uint;
GlyphEntry {
value: (glyphCount as u32) << GLYPH_COUNT_SHIFT
}
}
// Getters and setters for GlyphEntry. Setter methods are functional,
// because GlyphEntry is immutable and only a u32 in size.
impl GlyphEntry {
// getter methods
pure fn advance() -> Au {
assert self.is_simple();
from_int(((self.value & GLYPH_ADVANCE_MASK) >> GLYPH_ADVANCE_SHIFT) as int)
}
pure fn index() -> GlyphIndex {
assert self.is_simple();
self.value & GLYPH_ID_MASK
}
pure fn offset() -> Point2D<Au> {
assert self.is_simple();
Point2D(Au(0), Au(0))
}
pure fn is_ligature_start() -> bool {
self.has_flag(!FLAG_NOT_LIGATURE_GROUP_START)
}
pure fn is_cluster_start() -> 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.
pure fn char_is_space() -> bool {
self.has_flag(FLAG_CHAR_IS_SPACE)
}
pure fn char_is_tab() -> bool {
!self.is_simple() && self.has_flag(FLAG_CHAR_IS_TAB)
}
pure fn char_is_newline() -> bool {
!self.is_simple() && self.has_flag(FLAG_CHAR_IS_NEWLINE)
}
pure fn can_break_before() -> BreakType {
let flag = ((self.value & FLAG_CAN_BREAK_MASK) >> FLAG_CAN_BREAK_SHIFT) as u8;
break_flag_to_enum(flag)
}
// setter methods
pure fn set_char_is_space() -> GlyphEntry {
GlyphEntry(self.value | FLAG_CHAR_IS_SPACE)
}
pure fn set_char_is_tab() -> GlyphEntry {
assert !self.is_simple();
GlyphEntry(self.value | FLAG_CHAR_IS_TAB)
}
pure fn set_char_is_newline() -> GlyphEntry {
assert !self.is_simple();
GlyphEntry(self.value | FLAG_CHAR_IS_NEWLINE)
}
// returns a glyph entry only if the setting had changed.
pure fn set_can_break_before(e: BreakType) -> Option<GlyphEntry> {
let flag = break_enum_to_flag(e);
let mask = (flag as u32) << FLAG_CAN_BREAK_SHIFT;
let toggle = mask ^ (self.value & FLAG_CAN_BREAK_MASK);
match (toggle as bool) {
true => Some(GlyphEntry(self.value ^ toggle)),
false => None
}
}
// helper methods
/*priv*/ pure fn glyph_count() -> u16 {
assert !self.is_simple();
((self.value & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT) as u16
}
pure fn is_simple() -> bool {
self.has_flag(FLAG_IS_SIMPLE_GLYPH)
}
/*priv*/ pure fn has_flag(flag: u32) -> bool {
(self.value & flag) != 0
}
}
// 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.
struct DetailedGlyph {
index: GlyphIndex,
// 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>
}
fn DetailedGlyph(index: GlyphIndex,
advance: Au, offset: Point2D<Au>) -> DetailedGlyph {
DetailedGlyph {
index: index,
advance: advance,
offset: offset
}
}
struct DetailedGlyphRecord {
// source string offset/GlyphEntry offset in the TextRun
entry_offset: uint,
// offset into the detailed glyphs buffer
detail_offset: uint
}
impl DetailedGlyphRecord : Ord {
pure fn lt(other: &DetailedGlyphRecord) -> bool { self.entry_offset < other.entry_offset }
pure fn le(other: &DetailedGlyphRecord) -> bool { self.entry_offset <= other.entry_offset }
pure fn ge(other: &DetailedGlyphRecord) -> bool { self.entry_offset >= other.entry_offset }
pure fn gt(other: &DetailedGlyphRecord) -> bool { self.entry_offset > other.entry_offset }
}
impl DetailedGlyphRecord : Eq {
pure fn eq(other : &DetailedGlyphRecord) -> bool { self.entry_offset == other.entry_offset }
pure fn ne(other : &DetailedGlyphRecord) -> bool { self.entry_offset != 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 {
detail_buffer: DVec<DetailedGlyph>,
detail_lookup: DVec<DetailedGlyphRecord>,
mut lookup_is_sorted: bool,
}
fn DetailedGlyphStore() -> DetailedGlyphStore {
DetailedGlyphStore {
detail_buffer: DVec(),
detail_lookup: DVec(),
lookup_is_sorted: false
}
}
impl DetailedGlyphStore {
fn add_detailed_glyphs_for_entry(entry_offset: uint, glyphs: &[DetailedGlyph]) {
let entry = DetailedGlyphRecord {
entry_offset: entry_offset,
detail_offset: self.detail_buffer.len()
};
/* 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;
}
// not pure; may perform a deferred sort.
fn get_detailed_glyphs_for_entry(&self, entry_offset: uint, count: u16) -> &[DetailedGlyph] {
assert count > 0;
assert (count as uint) <= self.detail_buffer.len();
self.ensure_sorted();
let key = DetailedGlyphRecord {
entry_offset: entry_offset,
detail_offset: 0 // unused
};
do self.detail_lookup.borrow |records : &[DetailedGlyphRecord]| {
match records.binary_search_index(&key) {
None => fail ~"Invalid index not found in detailed glyph lookup table!",
Some(i) => {
do self.detail_buffer.borrow |glyphs : &[DetailedGlyph]| {
assert i + (count as uint) <= glyphs.len();
// return a view into the buffer
vec::view(glyphs, i, i + count as uint)
}
}
}
}
}
fn get_detailed_glyph_with_index(&self, entry_offset: uint, detail_offset: u16) -> &DetailedGlyph {
assert (detail_offset as uint) <= self.detail_buffer.len();
self.ensure_sorted();
let key = DetailedGlyphRecord {
entry_offset: entry_offset,
detail_offset: 0 // unused
};
do self.detail_lookup.borrow |records : &[DetailedGlyphRecord]| {
match records.binary_search_index(&key) {
None => fail ~"Invalid index not found in detailed glyph lookup table!",
Some(i) => {
do self.detail_buffer.borrow |glyphs : &[DetailedGlyph]| {
assert i + (detail_offset as uint) < glyphs.len();
&glyphs[i+(detail_offset as uint)]
}
}
}
}
}
/*priv*/ fn ensure_sorted() {
if self.lookup_is_sorted {
return;
}
do self.detail_lookup.borrow_mut |arr| {
sort::quick_sort3(arr);
};
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.
struct GlyphData {
index: GlyphIndex,
advance: Au,
offset: Point2D<Au>,
is_missing: bool,
cluster_start: bool,
ligature_start: bool,
}
pure fn GlyphData(index: GlyphIndex,
advance: Au,
offset: Option<Point2D<Au>>,
is_missing: bool,
cluster_start: bool,
ligature_start: bool) -> GlyphData {
let _offset = match offset {
None => au::zero_point(),
Some(o) => o
};
GlyphData {
index: index,
advance: advance,
offset: _offset,
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.
enum GlyphInfo {
SimpleGlyphInfo(&GlyphStore, uint),
DetailGlyphInfo(&GlyphStore, uint, u16)
}
impl GlyphInfo {
fn index() -> GlyphIndex {
match self {
SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i].index(),
DetailGlyphInfo(store, entry_i, detail_j) => store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).index
}
}
fn advance() -> Au {
match self {
SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i].advance(),
DetailGlyphInfo(store, entry_i, detail_j) => store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).advance
}
}
fn offset() -> 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)
}
}
fn is_ligature_start() -> bool {
match self {
SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i].is_ligature_start(),
DetailGlyphInfo(store, entry_i, _) => store.entry_buffer[entry_i].is_ligature_start()
}
}
fn is_cluster_start() -> bool {
match self {
SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i].is_cluster_start(),
DetailGlyphInfo(store, entry_i, _) => store.entry_buffer[entry_i].is_cluster_start()
}
}
}
// Public data structure and API for storing and retrieving glyph data
struct GlyphStore {
// we use a DVec here instead of a mut vec, since this is much safer.
entry_buffer: DVec<GlyphEntry>,
detail_store: DetailedGlyphStore,
}
// Initializes the glyph store, but doesn't actually shape anything.
// Use the set_glyph, set_glyphs() methods to store glyph data.
fn GlyphStore(length: uint) -> GlyphStore {
assert length > 0;
let buffer = vec::from_elem(length, InitialGlyphEntry());
GlyphStore {
entry_buffer: dvec::from_vec(move buffer),
detail_store: DetailedGlyphStore(),
}
}
impl GlyphStore {
fn add_glyph_for_index(i: uint, data: &GlyphData) {
pure fn glyph_is_compressible(data: &GlyphData) -> bool {
is_simple_glyph_id(data.index)
&& is_simple_advance(data.advance)
&& data.offset == au::zero_point()
}
assert i < self.entry_buffer.len();
let entry = match (data.is_missing, glyph_is_compressible(data)) {
(true, _) => MissingGlyphsEntry(1),
(false, true) => { SimpleGlyphEntry(data.index, data.advance) },
(false, false) => {
let glyph = [DetailedGlyph(data.index, data.advance, data.offset)];
self.detail_store.add_detailed_glyphs_for_entry(i, glyph);
ComplexGlyphEntry(data.cluster_start, data.ligature_start, 1)
}
};
self.entry_buffer.set_elt(i, entry);
}
fn add_glyphs_for_index(i: uint, data_for_glyphs: &[GlyphData]) {
assert i < self.entry_buffer.len();
assert data_for_glyphs.len() > 0;
let glyph_count = data_for_glyphs.len();
let first_glyph_data = data_for_glyphs[0];
let entry = match first_glyph_data.is_missing {
true => MissingGlyphsEntry(glyph_count),
false => {
let glyphs_vec = vec::from_fn(glyph_count, |i| {
DetailedGlyph(data_for_glyphs[i].index,
data_for_glyphs[i].advance,
data_for_glyphs[i].offset)
});
self.detail_store.add_detailed_glyphs_for_entry(i, glyphs_vec);
ComplexGlyphEntry(first_glyph_data.cluster_start,
first_glyph_data.ligature_start,
glyph_count)
}
};
self.entry_buffer.set_elt(i, entry);
}
fn iter_glyphs_for_index<T>(&self, i: uint, cb: fn&(uint, GlyphInfo/&) -> T) {
assert i < self.entry_buffer.len();
let entry = &self.entry_buffer[i];
match entry.is_simple() {
true => {
let proxy = SimpleGlyphInfo(self, i);
cb(i, proxy);
},
false => {
let glyphs = self.detail_store.get_detailed_glyphs_for_entry(i, entry.glyph_count());
for uint::range(0, glyphs.len()) |j| {
let proxy = DetailGlyphInfo(self, i, j as u16);
cb(i, proxy);
}
}
}
}
fn iter_glyphs_for_range<T>(&self, range: Range, cb: fn&(uint, GlyphInfo/&) -> T) {
assert range.begin() < self.entry_buffer.len();
assert range.end() <= self.entry_buffer.len();
for range.eachi |i| { self.iter_glyphs_for_index(i, cb); }
}
fn iter_all_glyphs<T>(cb: fn&(uint, GlyphInfo/&) -> T) {
for uint::range(0, self.entry_buffer.len()) |i| {
self.iter_glyphs_for_index(i, cb);
}
}
// getter methods
fn char_is_space(i: uint) -> bool {
assert i < self.entry_buffer.len();
self.entry_buffer[i].char_is_space()
}
fn char_is_tab(i: uint) -> bool {
assert i < self.entry_buffer.len();
self.entry_buffer[i].char_is_tab()
}
fn char_is_newline(i: uint) -> bool {
assert i < self.entry_buffer.len();
self.entry_buffer[i].char_is_newline()
}
fn is_ligature_start(i: uint) -> bool {
assert i < self.entry_buffer.len();
self.entry_buffer[i].is_ligature_start()
}
fn is_cluster_start(i: uint) -> bool {
assert i < self.entry_buffer.len();
self.entry_buffer[i].is_cluster_start()
}
fn can_break_before(i: uint) -> BreakType {
assert i < self.entry_buffer.len();
self.entry_buffer[i].can_break_before()
}
// setter methods
fn set_char_is_space(i: uint) {
assert i < self.entry_buffer.len();
let entry = self.entry_buffer[i];
self.entry_buffer.set_elt(i, entry.set_char_is_space())
}
fn set_char_is_tab(i: uint) {
assert i < self.entry_buffer.len();
let entry = self.entry_buffer[i];
self.entry_buffer.set_elt(i, entry.set_char_is_tab())
}
fn set_char_is_newline(i: uint) {
assert i < self.entry_buffer.len();
let entry = self.entry_buffer[i];
self.entry_buffer.set_elt(i, entry.set_char_is_newline())
}
fn set_can_break_before(i: uint, t: BreakType) {
assert i < self.entry_buffer.len();
let entry = self.entry_buffer[i];
match entry.set_can_break_before(t) {
Some(e) => self.entry_buffer.set_elt(i, e),
None => {}
};
}
}

View file

@ -1,191 +0,0 @@
extern mod harfbuzz;
use geom::Point2D;
use gfx::au;
use gfx::{
Au,
Font,
};
use glyph::{GlyphStore, GlyphIndex, GlyphData};
use libc::types::common::c99::int32_t;
use libc::{c_uint, c_int, c_void, c_char};
use ptr::{null, to_unsafe_ptr, offset};
use std::arc;
use harfbuzz::{HB_MEMORY_MODE_READONLY,
HB_DIRECTION_LTR};
use harfbuzz::{hb_blob_t, hb_face_t, hb_font_t, hb_font_funcs_t, hb_buffer_t,
hb_codepoint_t, hb_bool_t, hb_glyph_position_t,
hb_glyph_info_t, hb_var_int_t, hb_position_t};
use harfbuzz::bindgen::{hb_blob_create, hb_blob_destroy,
hb_face_create, hb_face_destroy,
hb_font_create, hb_font_destroy,
hb_buffer_create, hb_buffer_destroy,
hb_buffer_add_utf8, hb_shape,
hb_buffer_get_glyph_infos,
hb_buffer_get_glyph_positions,
hb_font_set_ppem, hb_font_set_scale,
hb_buffer_set_direction,
hb_font_funcs_create, hb_font_funcs_destroy,
hb_font_set_funcs,
hb_font_funcs_set_glyph_h_advance_func,
hb_font_funcs_set_glyph_func,
hb_font_funcs_set_glyph_h_kerning_func};
pub struct HarfbuzzShaper {
priv font: @Font,
priv hb_blob: *hb_blob_t,
priv hb_face: *hb_face_t,
priv hb_font: *hb_font_t,
priv hb_funcs: *hb_font_funcs_t,
drop {
assert self.hb_blob.is_not_null();
hb_blob_destroy(self.hb_blob);
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);
}
}
pub impl HarfbuzzShaper {
static pub fn new(font: @Font) -> HarfbuzzShaper {
// TODO(Issue #92): font tables should be stored in Font object and cached per-task
let hb_blob: *hb_blob_t = vec::as_imm_buf(*(font).buf(), |buf: *u8, len: uint| {
hb_blob_create(buf as *c_char,
len as c_uint,
HB_MEMORY_MODE_READONLY,
null(),
null())
});
let hb_face: *hb_face_t = hb_face_create(hb_blob, 0 as c_uint);
let hb_font: *hb_font_t = hb_font_create(hb_face);
// Set points-per-em. if zero, performs no hinting in that direction.
let pt_size = font.style.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,
HarfbuzzShaper::float_to_fixed(pt_size) as c_int,
HarfbuzzShaper::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: *hb_font_funcs_t = hb_font_funcs_create();
hb_font_funcs_set_glyph_func(hb_funcs, glyph_func, null(), null());
hb_font_funcs_set_glyph_h_advance_func(hb_funcs, glyph_h_advance_func, null(), null());
unsafe {
let font_data: *c_void = core::ptr::addr_of(font) as *c_void;
hb_font_set_funcs(hb_font, hb_funcs, font_data, null());
};
HarfbuzzShaper {
font: font,
hb_blob: hb_blob,
hb_face: hb_face,
hb_font: hb_font,
hb_funcs: hb_funcs,
}
}
/**
Calculate the layout metrics associated with a some given text
when rendered in a specific font.
*/
pub fn shape_text(text: &str, glyphs: &GlyphStore) {
debug!("shaping text '%s'", text);
// TODO(Issue #94): harfbuzz fonts and faces should be cached on the
// Shaper object, which is owned by the Font instance.
let hb_buffer: *hb_buffer_t = hb_buffer_create();
hb_buffer_set_direction(hb_buffer, HB_DIRECTION_LTR);
// Using as_buf because it never does a copy - we don't need the trailing null
str::as_buf(text, |ctext: *u8, _l: uint| {
hb_buffer_add_utf8(hb_buffer,
ctext as *c_char,
text.len() as c_int,
0 as c_uint,
text.len() as c_int);
});
hb_shape(self.hb_font, hb_buffer, null(), 0 as c_uint);
let info_buf_len = 0 as c_uint;
let info_buf = hb_buffer_get_glyph_infos(hb_buffer, to_unsafe_ptr(&info_buf_len));
assert info_buf.is_not_null();
let pos_buf_len = 0 as c_uint;
let pos_buf = hb_buffer_get_glyph_positions(hb_buffer, to_unsafe_ptr(&pos_buf_len));
assert pos_buf.is_not_null();
assert info_buf_len == pos_buf_len;
for uint::range(0u, info_buf_len as uint) |i| { unsafe {
let hb_info: hb_glyph_info_t = *offset(info_buf, i);
let hb_pos: hb_glyph_position_t = *offset(pos_buf, i);
let codepoint = hb_info.codepoint as GlyphIndex;
let advance: Au = au::from_frac_px(HarfbuzzShaper::fixed_to_float(hb_pos.x_advance));
let offset = match (hb_pos.x_offset, hb_pos.y_offset) {
(0, 0) => None,
(x, y) => Some(Point2D(au::from_frac_px(HarfbuzzShaper::fixed_to_float(x)),
au::from_frac_px(HarfbuzzShaper::fixed_to_float(y))))
};
// TODO: convert pos.y_advance into offset adjustment
// TODO: handle multiple glyphs per char, ligatures, etc.
// NB. this debug statement is commented out, as it must be checked for every shaped char.
//debug!("glyph %?: index %?, advance %?, offset %?", i, codepoint, advance, offset);
let data = GlyphData(codepoint, advance, offset, false, false, false);
glyphs.add_glyph_for_index(i, &data);
} /* unsafe */ }
hb_buffer_destroy(hb_buffer);
}
static priv fn float_to_fixed(f: float) -> i32 {
util::float_to_fixed(16, f)
}
static priv fn fixed_to_float(i: hb_position_t) -> float {
util::fixed_to_float(16, i)
}
static priv fn fixed_to_rounded_int(f: hb_position_t) -> int {
util::fixed_to_rounded_int(16, f)
}
}
/// Callbacks from Harfbuzz when font map and glyph advance lookup needed.
extern fn glyph_func(_font: *hb_font_t,
font_data: *c_void,
unicode: hb_codepoint_t,
_variant_selector: hb_codepoint_t,
glyph: *mut hb_codepoint_t,
_user_data: *c_void) -> hb_bool_t unsafe {
let font: *Font = font_data as *Font;
assert font.is_not_null();
return match (*font).glyph_index(unicode as char) {
Some(g) => { *glyph = g as hb_codepoint_t; true },
None => false
} as hb_bool_t;
}
extern fn glyph_h_advance_func(_font: *hb_font_t,
font_data: *c_void,
glyph: hb_codepoint_t,
_user_data: *c_void) -> hb_position_t unsafe {
let font: *Font = font_data as *Font;
assert font.is_not_null();
let advance = (*font).glyph_h_advance(glyph as GlyphIndex);
HarfbuzzShaper::float_to_fixed(advance)
}

View file

@ -1,17 +0,0 @@
/**
Shaper encapsulates a specific shaper, such as Harfbuzz,
Uniscribe, Pango, or Coretext.
Currently, only harfbuzz bindings are implemented.
*/
use gfx::Font;
pub type Shaper/& = harfbuzz::shaper::HarfbuzzShaper;
// TODO(Issue #163): this is a workaround for static methods and
// typedefs not working well together. It should be removed.
impl Shaper {
static pub fn new(font: @Font) -> Shaper {
harfbuzz::shaper::HarfbuzzShaper::new(font)
}
}

View file

@ -1,210 +0,0 @@
use arc = std::arc;
use arc::ARC;
use geom::point::Point2D;
use geom::size::Size2D;
use gfx::au;
use gfx::{
Au,
Font,
FontContext,
FontDescriptor,
RunMetrics,
};
use glyph::GlyphStore;
use layout::context::LayoutContext;
use libc::{c_void};
use newcss::color;
use std::arc;
use servo_util::range::{Range, MutableRange};
pub struct TextRun {
text: ~str,
font: @Font,
priv glyphs: GlyphStore,
}
// This is a hack until TextRuns are normally sendable, or
// we instead use ARC<TextRun> everywhere.
pub struct SendableTextRun {
text: ~str,
font: FontDescriptor,
priv glyphs: GlyphStore,
}
impl SendableTextRun {
pub fn deserialize(&self, fctx: @FontContext) -> TextRun {
let font = match fctx.get_font_by_descriptor(&self.font) {
Ok(f) => f,
Err(_) => fail fmt!("Font descriptor deserialization failed! desc=%?", self.font)
};
TextRun {
text: copy self.text,
font: font,
glyphs: copy self.glyphs
}
}
}
impl TextRun {
static fn new(font: @Font, text: ~str) -> TextRun {
let glyph_store = font.shape_text(text);
let run = TextRun {
text: move text,
font: font,
glyphs: move glyph_store,
};
return move run;
}
pub fn serialize(&self) -> SendableTextRun {
SendableTextRun {
text: copy self.text,
font: self.font.get_descriptor(),
glyphs: copy self.glyphs,
}
}
pure fn glyphs(&self) -> &self/GlyphStore { &self.glyphs }
pure fn range_is_trimmable_whitespace(&self, range: Range) -> bool {
let mut i = range.begin();
while i < range.end() {
// jump i to each new char
let {ch, next} = str::char_range_at(self.text, i);
match ch {
' ' | '\t' | '\r' => {},
_ => { return false; }
}
i = next;
}
return true;
}
fn metrics_for_range(&self, range: Range) -> RunMetrics {
self.font.measure_text(self, range)
}
fn min_width_for_range(&self, range: Range) -> Au {
assert range.is_valid_for_string(self.text);
let mut max_piece_width = Au(0);
for self.iter_indivisible_pieces_for_range(range) |piece_range| {
let metrics = self.font.measure_text(self, piece_range);
max_piece_width = au::max(max_piece_width, metrics.advance_width);
}
return max_piece_width;
}
fn iter_natural_lines_for_range(&self, range: Range, f: fn(Range) -> bool) {
assert range.is_valid_for_string(self.text);
let clump = MutableRange(range.begin(), 0);
let mut in_clump = false;
// clump non-linebreaks of nonzero length
for range.eachi |i| {
match (self.glyphs.char_is_newline(i), in_clump) {
(false, true) => { clump.extend_by(1); }
(false, false) => { in_clump = true; clump.reset(i, 1); }
(true, false) => { /* chomp whitespace */ }
(true, true) => {
in_clump = false;
// don't include the linebreak 'glyph'
// (we assume there's one GlyphEntry for a newline, and no actual glyphs)
if !f(clump.as_immutable()) { break }
}
}
}
// flush any remaining chars as a line
if in_clump {
clump.extend_to(range.end());
f(clump.as_immutable());
}
}
fn iter_indivisible_pieces_for_range(&self, range: Range, f: fn(Range) -> bool) {
assert range.is_valid_for_string(self.text);
let clump = MutableRange(range.begin(), 0);
loop {
// find next non-whitespace byte index, then clump all whitespace before it.
match str::find_between(self.text, clump.begin(), range.end(), |c| !char::is_whitespace(c)) {
Some(nonws_char_offset) => {
clump.extend_to(nonws_char_offset);
if !f(clump.as_immutable()) { break }
clump.reset(clump.end(), 0);
},
None => {
// nothing left, flush last piece containing only whitespace
if clump.end() < range.end() {
clump.extend_to(range.end());
f(clump.as_immutable());
break;
}
}
};
// find next whitespace byte index, then clump all non-whitespace before it.
match str::find_between(self.text, clump.begin(), range.end(), |c| char::is_whitespace(c)) {
Some(ws_char_offset) => {
clump.extend_to(ws_char_offset);
if !f(clump.as_immutable()) { break }
clump.reset(clump.end(), 0);
}
None => {
// nothing left, flush last piece containing only non-whitespaces
if clump.end() < range.end() {
clump.extend_to(range.end());
f(clump.as_immutable());
break;
}
}
}
}
}
}
// this test can't run until LayoutContext is removed as an argument
// to min_width_for_range.
/*
#[test]
fn test_calc_min_break_width() {
fn test_min_width_for_run(text: ~str, width: Au) {
let flib = FontCache();
let font = flib.get_test_font();
let run = TextRun(font, text);
run.min_width_for_range(0, text.len())
}
test_min_width_for_run(~"firecracker", au::from_px(84));
test_min_width_for_run(~"firecracker yumyum", au::from_px(84));
test_min_width_for_run(~"yumyum firecracker", au::from_px(84));
test_min_width_for_run(~"yumyum firecracker yumyum", au::from_px(84));
}
*/
/*#[test]
#[ignore]
fn test_iter_indivisible_pieces() {
fn test_pieces(text: ~str, res: ~[~str]) {
let flib = FontCache();
let font = flib.get_test_font();
let run = TextRun::new(font, copy text);
let mut slices : ~[~str] = ~[];
for run.iter_indivisible_pieces_for_range(Range(0, text.len())) |subrange| {
slices.push(str::slice(text, subrange.begin(), subrange.length()));
}
assert slices == res;
}
test_pieces(~"firecracker yumyum woopwoop", ~[~"firecracker", ~" ", ~"yumyum", ~" ", ~"woopwoop"]);
test_pieces(~"firecracker yumyum ", ~[~"firecracker", ~" ", ~"yumyum", ~" "]);
test_pieces(~" firecracker yumyum", ~[~" ", ~"firecracker", ~" ", ~"yumyum"]);
test_pieces(~" ", ~[~" "]);
test_pieces(~"", ~[]);
}
*/

View file

@ -1,223 +0,0 @@
enum CompressionMode {
CompressNone,
CompressWhitespace,
CompressWhitespaceNewline,
DiscardNewline
}
impl CompressionMode : cmp::Eq {
pure fn eq(other: &CompressionMode) -> bool {
match (self, *other) {
(CompressNone, CompressNone) => true,
(CompressWhitespace, CompressWhitespace) => true,
(CompressWhitespaceNewline, CompressWhitespaceNewline) => true,
(DiscardNewline, DiscardNewline) => true,
_ => false
}
}
pure fn ne(other: &CompressionMode) -> bool {
!self.eq(other)
}
}
// ported from Gecko's nsTextFrameUtils::TransformText.
//
// High level TODOs:
//
// * Issue #113: consider incoming text state (preceding spaces, 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) -> ~str {
let mut out_str: ~str = ~"";
match mode {
CompressNone | DiscardNewline => {
for str::each_char(text) |ch: char| {
if is_discardable_char(ch, mode) {
// TODO: record skipped char
} else {
// TODO: record kept char
if ch == '\t' {
// TODO: set "has tab" flag
}
str::push_char(&mut out_str, ch);
}
}
},
CompressWhitespace | CompressWhitespaceNewline => {
let mut in_whitespace: bool = false;
for str::each_char(text) |ch: char| {
// TODO: discard newlines between CJK chars
let mut next_in_whitespace: bool = match (ch, mode) {
(' ', _) => true,
('\t', _) => true,
('\n', CompressWhitespaceNewline) => true,
(_, _) => false
};
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
str::push_char(&mut out_str, ch);
}
} else { /* next_in_whitespace; possibly add a space char */
if in_whitespace {
// TODO: record skipped char
} else {
// TODO: record kept char
str::push_char(&mut out_str, ' ');
}
}
// save whitespace context for next char
in_whitespace = next_in_whitespace;
} /* /for str::each_char */
}
}
return move out_str;
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: float) -> i32 {
(1i32 << before) * (f as i32)
}
pub fn fixed_to_float(before: int, f: i32) -> float {
f as float * 1.0f / ((1i32 << before) as float)
}
pub fn fixed_to_rounded_int(before: int, f: i32) -> int {
let half = 1i32 << (before-1);
if f > 0i32 {
((half + f) >> before) as int
} else {
-((half - f) >> before) 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 {
(a << 24 | b << 16 | c << 8 | d) as u32
}
#[test]
fn test_true_type_tag() {
assert true_type_tag('c', 'm', 'a', 'p') == 0x_63_6D_61_70_u32;
}
#[test]
fn test_transform_compress_none() {
let test_strs : ~[~str] = ~[~" foo bar",
~"foo bar ",
~"foo\n bar",
~"foo \nbar",
~" foo bar \nbaz",
~"foo bar baz",
~"foobarbaz\n\n"];
let mode = CompressNone;
for uint::range(0, test_strs.len()) |i| {
assert transform_text(test_strs[i], mode) == test_strs[i];
}
}
#[test]
fn test_transform_discard_newline() {
let test_strs : ~[~str] = ~[~" foo bar",
~"foo bar ",
~"foo\n bar",
~"foo \nbar",
~" foo bar \nbaz",
~"foo bar baz",
~"foobarbaz\n\n"];
let oracle_strs : ~[~str] = ~[~" foo bar",
~"foo bar ",
~"foo bar",
~"foo bar",
~" foo bar baz",
~"foo bar baz",
~"foobarbaz"];
assert test_strs.len() == oracle_strs.len();
let mode = DiscardNewline;
for uint::range(0, test_strs.len()) |i| {
assert transform_text(test_strs[i], mode) == oracle_strs[i];
}
}
#[test]
fn test_transform_compress_whitespace() {
let test_strs : ~[~str] = ~[~" foo bar",
~"foo bar ",
~"foo\n bar",
~"foo \nbar",
~" foo bar \nbaz",
~"foo bar baz",
~"foobarbaz\n\n"];
let oracle_strs : ~[~str] = ~[~" foo bar",
~"foo bar ",
~"foo\n bar",
~"foo \nbar",
~" foo bar \nbaz",
~"foo bar baz",
~"foobarbaz\n\n"];
assert test_strs.len() == oracle_strs.len();
let mode = CompressWhitespace;
for uint::range(0, test_strs.len()) |i| {
assert transform_text(test_strs[i], mode) == oracle_strs[i];
}
}
#[test]
fn test_transform_compress_whitespace_newline() {
let test_strs : ~[~str] = ~[~" foo bar",
~"foo bar ",
~"foo\n bar",
~"foo \nbar",
~" foo bar \nbaz",
~"foo bar baz",
~"foobarbaz\n\n"];
let oracle_strs : ~[~str] = ~[~" foo bar",
~"foo bar ",
~"foo bar",
~"foo bar",
~" foo bar baz",
~"foo bar baz",
~"foobarbaz "];
assert test_strs.len() == oracle_strs.len();
let mode = CompressWhitespaceNewline;
for uint::range(0, test_strs.len()) |i| {
assert transform_text(test_strs[i], mode) == oracle_strs[i];
}
}

View file

@ -1,59 +0,0 @@
use core::cmp::*;
trait Cache<K: Copy Eq, V: Copy> {
static fn new(size: uint) -> self;
fn insert(key: &K, value: V);
fn find(key: &K) -> Option<V>;
fn find_or_create(key: &K, blk: pure fn&(&K) -> V) -> V;
fn evict_all();
}
pub struct MonoCache<K: Copy Eq, V: Copy> {
mut entry: Option<(K,V)>,
}
pub impl<K: Copy Eq, V: Copy> MonoCache<K,V> : Cache<K,V> {
static fn new(_size: uint) -> MonoCache<K,V> {
MonoCache { entry: None }
}
fn insert(key: &K, value: V) {
self.entry = Some((copy *key, value));
}
fn find(key: &K) -> Option<V> {
match self.entry {
None => None,
Some((ref k,v)) => if *k == *key { Some(v) } else { None }
}
}
fn find_or_create(key: &K, blk: pure fn&(&K) -> V) -> V {
return match self.find(key) {
None => {
let value = blk(key);
self.entry = Some((copy *key, copy value));
move value
},
Some(v) => v
};
}
fn evict_all() {
self.entry = None;
}
}
#[test]
fn test_monocache() {
// TODO: this is hideous because of Rust Issue #3902
let cache = cache::new::<uint, @str, MonoCache<uint, @str>>(10);
let one = @"one";
let two = @"two";
cache.insert(&1, one);
assert cache.find(&1).is_some();
assert cache.find(&2).is_none();
cache.find_or_create(&2, |_v| { two });
assert cache.find(&2).is_some();
assert cache.find(&1).is_none();
}

View file

@ -1,158 +0,0 @@
pub struct Range {
priv off: u16,
priv len: u16
}
pub pure fn Range(off: uint, len: uint) -> Range {
assert off <= u16::max_value as uint;
assert len <= u16::max_value as uint;
Range {
off: off as u16,
len: len as u16
}
}
pub pure fn empty() -> Range { Range(0,0) }
enum RangeRelation {
OverlapsBegin(/* overlap */ uint),
OverlapsEnd(/* overlap */ uint),
ContainedBy,
Contains,
Coincides,
EntirelyBefore,
EntirelyAfter
}
pub impl Range {
pub pure fn begin() -> uint { self.off as uint }
pub pure fn length() -> uint { self.len as uint }
pub pure fn end() -> uint { (self.off as uint) + (self.len as uint) }
pub pure fn eachi(cb: fn&(uint) -> bool) {
do uint::range(self.off as uint,
(self.off as uint) + (self.len as uint)) |i| {
cb(i)
}
}
pub pure fn is_valid_for_string(s: &str) -> bool {
self.begin() < s.len() && self.end() <= s.len() && self.length() <= s.len()
}
pub pure fn shift_by(i: int) -> Range {
Range(((self.off as int) + i) as uint, self.len as uint)
}
pub pure fn extend_by(i: int) -> Range {
Range(self.off as uint, ((self.len as int) + i) as uint)
}
pub pure fn adjust_by(off_i: int, len_i: int) -> Range {
Range(((self.off as int) + off_i) as uint, ((self.len as int) + len_i) as uint)
}
/// Computes the relationship between two ranges (`self` and `other`),
/// from the point of view of `self`. So, 'EntirelyBefore' means
/// that the `self` range is entirely before `other` range.
fn relation_to_range(&self, other: Range) -> RangeRelation {
if other.begin() > self.end() {
return EntirelyBefore;
}
if self.begin() > other.end() {
return EntirelyAfter;
}
if self.begin() == other.begin() && self.end() == other.end() {
return Coincides;
}
if self.begin() <= other.begin() && self.end() >= other.end() {
return Contains;
}
if self.begin() >= other.begin() && self.end() <= other.end() {
return ContainedBy;
}
if self.begin() < other.begin() && self.end() < other.end() {
let overlap = self.end() - other.begin();
return OverlapsBegin(overlap);
}
if self.begin() > other.begin() && self.end() > other.end() {
let overlap = other.end() - self.begin();
return OverlapsEnd(overlap);
}
fail fmt!("relation_to_range(): didn't classify self=%?, other=%?",
self, other);
}
fn repair_after_coalesced_range(&self, other: Range) -> Range {
let relation = self.relation_to_range(other);
debug!("repair_after_coalesced_range: possibly repairing range %?", self);
debug!("repair_after_coalesced_range: relation of original range and coalesced range(%?): %?",
other, relation);
let new_range = match relation {
EntirelyBefore => { *self },
EntirelyAfter => { self.shift_by(-(other.length() as int)) },
Coincides | ContainedBy => { Range(other.begin(), 1) },
Contains => { self.extend_by(-(other.length() as int)) },
OverlapsBegin(overlap) => { self.extend_by(1 - (overlap as int)) },
OverlapsEnd(overlap) =>
{ Range(other.begin(), self.length() - overlap + 1) }
};
debug!("repair_after_coalesced_range: new range: ---- %?", new_range);
new_range
}
}
pub pure fn empty_mut() -> MutableRange { MutableRange(0,0) }
pub struct MutableRange {
priv mut off: uint,
priv mut len: uint
}
pure fn MutableRange(off: uint, len :uint) -> MutableRange {
MutableRange { off: off, len: len }
}
impl MutableRange {
pub pure fn begin() -> uint { self.off }
pub pure fn length() -> uint { self.len }
pub pure fn end() -> uint { self.off + self.len }
pub pure fn eachi(cb: fn&(uint) -> bool) {
do uint::range(self.off, self.off + self.len) |i| { cb(i) }
}
fn relation_to_range(&self, other: &MutableRange) -> RangeRelation {
self.as_immutable().relation_to_range(other.as_immutable())
}
pub pure fn as_immutable() -> Range {
Range(self.begin(), self.length())
}
pub pure fn is_valid_for_string(s: &str) -> bool {
self.begin() < s.len() && self.end() <= s.len() && self.length() <= s.len()
}
pub fn shift_by(i: int) {
self.off = ((self.off as int) + i) as uint;
}
pub fn extend_by(i: int) {
self.len = ((self.len as int) + i) as uint;
}
pub fn extend_to(i: uint) {
self.len = i - self.off;
}
pub fn adjust_by(off_i: int, len_i: int) {
self.off = ((self.off as int) + off_i) as uint;
self.len = ((self.len as int) + len_i) as uint;
}
pub fn reset(off_i: uint, len_i: uint) {
self.off = off_i;
self.len = len_i;
}
}

View file

@ -1,15 +0,0 @@
// Timing functions.
use std::time::precise_time_ns;
pub fn time<T>(msg: &str, callback: fn() -> T) -> T{
let start_time = precise_time_ns();
let val = callback();
let end_time = precise_time_ns();
let ms = ((end_time - start_time) / 1000000u64) as uint;
if ms >= 5 {
#debug("%s took %u ms", msg, ms);
}
return move val;
}

View file

@ -1,109 +0,0 @@
export make_url, UrlMap, url_map;
use std::net::url;
use std::net::url::Url;
use std::map::HashMap;
use path::Path;
/**
Create a URL object from a string. Does various helpful browsery things like
* If there's no current url and the path looks like a file then it will
create a file url based of the current working directory
* If there's a current url and the new path is relative then the new url
is based off the current url
*/
#[allow(non_implicitly_copyable_typarams)]
fn make_url(str_url: ~str, current_url: Option<Url>) -> Url {
let mut schm = url::get_scheme(str_url);
let str_url = if result::is_err(&schm) {
if current_url.is_none() {
// If all we have is a filename, assume it's a local relative file
// and build an absolute path with the cwd
~"file://" + os::getcwd().push(str_url).to_str()
} else {
let current_url = current_url.get();
#debug("make_url: current_url: %?", current_url);
if current_url.path.is_empty() || current_url.path.ends_with("/") {
current_url.scheme + "://" + current_url.host + "/" + str_url
} else {
let path = str::split_char(current_url.path, '/');
let path = path.init();
let path = str::connect(path + ~[move str_url], "/");
current_url.scheme + "://" + current_url.host + path
}
}
} else {
move str_url
};
// FIXME: Need to handle errors
url::from_str(str_url).get()
}
mod make_url_tests {
#[test]
fn should_create_absolute_file_url_if_current_url_is_none_and_str_url_looks_filey() {
let file = ~"local.html";
let url = make_url(move file, None);
#debug("url: %?", url);
assert url.scheme == ~"file";
assert url.path.contains(os::getcwd().to_str());
}
#[test]
fn should_create_url_based_on_old_url_1() {
let old_str = ~"http://example.com";
let old_url = make_url(move old_str, None);
let new_str = ~"index.html";
let new_url = make_url(move new_str, Some(move old_url));
assert new_url.scheme == ~"http";
assert new_url.host == ~"example.com";
assert new_url.path == ~"/index.html";
}
#[test]
fn should_create_url_based_on_old_url_2() {
let old_str = ~"http://example.com/";
let old_url = make_url(move old_str, None);
let new_str = ~"index.html";
let new_url = make_url(move new_str, Some(move old_url));
assert new_url.scheme == ~"http";
assert new_url.host == ~"example.com";
assert new_url.path == ~"/index.html";
}
#[test]
fn should_create_url_based_on_old_url_3() {
let old_str = ~"http://example.com/index.html";
let old_url = make_url(move old_str, None);
let new_str = ~"crumpet.html";
let new_url = make_url(move new_str, Some(move old_url));
assert new_url.scheme == ~"http";
assert new_url.host == ~"example.com";
assert new_url.path == ~"/crumpet.html";
}
#[test]
fn should_create_url_based_on_old_url_4() {
let old_str = ~"http://example.com/snarf/index.html";
let old_url = make_url(move old_str, None);
let new_str = ~"crumpet.html";
let new_url = make_url(move new_str, Some(move old_url));
assert new_url.scheme == ~"http";
assert new_url.host == ~"example.com";
assert new_url.path == ~"/snarf/crumpet.html";
}
}
type UrlMap<T: Copy> = HashMap<Url, T>;
fn url_map<T: Copy>() -> UrlMap<T> {
use core::to_str::ToStr;
HashMap::<Url, T>()
}

View file

@ -1,102 +0,0 @@
use core::cmp::{Ord, Eq};
export BinarySearchMethods, binary_search, binary_search_index;
trait BinarySearchMethods<T: Ord Eq> {
pure fn binary_search(&self, key: &T) -> Option<&self/T>;
pure fn binary_search_index(&self, key: &T) -> Option<uint>;
}
impl<T: Ord Eq> &[T]: BinarySearchMethods<T> {
pure fn binary_search(&self, key: &T) -> Option<&self/T> {
match self.binary_search_index(key) {
None => None,
Some(i) => Some(&self[i])
}
}
pure fn binary_search_index(&self, key: &T) -> Option<uint> {
if self.len() == 0 {
return None;
}
let mut low : int = 0;
let mut high : int = (self.len() as int) - 1;
while (low <= high) {
// http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html
let mid : int = (((low as uint) + (high as uint)) >> 1) as int;
let midv = &self[mid];
if (midv < key) {
low = mid + 1;
} else if (midv > key) {
high = mid - 1;
} else {
return Some(mid as uint);
}
}
return None;
}
}
fn test_find_all_elems<T: Eq Ord>(arr: &[T]) {
let mut i = 0;
while i < arr.len() {
assert test_match(&arr[i], arr.binary_search(&arr[i]));
i += 1;
}
}
fn test_miss_all_elems<T: Eq Ord>(arr: &[T], misses: &[T]) {
let mut i = 0;
while i < misses.len() {
let res = arr.binary_search(&misses[i]);
debug!("%? == %? ?", misses[i], res);
assert !test_match(&misses[i], arr.binary_search(&misses[i]));
i += 1;
}
}
fn test_match<T: Eq>(b: &T, a: Option<&T>) -> bool {
match a {
None => false,
Some(t) => t == b
}
}
fn should_find_all_elements() {
#[test];
let arr_odd = [1, 2, 4, 6, 7, 8, 9];
let arr_even = [1, 2, 5, 6, 7, 8, 9, 42];
let arr_double = [1, 1, 2, 2, 6, 8, 22];
let arr_one = [234986325];
let arr_two = [3044, 8393];
let arr_three = [12, 23, 34];
test_find_all_elems(arr_odd);
test_find_all_elems(arr_even);
test_find_all_elems(arr_double);
test_find_all_elems(arr_one);
test_find_all_elems(arr_two);
test_find_all_elems(arr_three);
}
fn should_not_find_missing_elements() {
#[test];
let arr_odd = [1, 2, 4, 6, 7, 8, 9];
let arr_even = [1, 2, 5, 6, 7, 8, 9, 42];
let arr_double = [1, 1, 2, 2, 6, 8, 22];
let arr_one = [234986325];
let arr_two = [3044, 8393];
let arr_three = [12, 23, 34];
test_miss_all_elems(arr_odd, [-22, 0, 3, 5, 34938, 10, 11, 12]);
test_miss_all_elems(arr_even, [-1, 0, 3, 34938, 10, 11, 12]);
test_miss_all_elems(arr_double, [-1, 0, 3, 4, 34938, 10, 11, 12, 234, 234, 33]);
test_miss_all_elems(arr_one, [-1, 0, 3, 34938, 10, 11, 12, 234, 234, 33]);
test_miss_all_elems(arr_two, [-1, 0, 3, 34938, 10, 11, 12, 234, 234, 33]);
test_miss_all_elems(arr_three, [-2, 0, 1, 2, 3, 34938, 10, 11, 234, 234, 33]);
}