mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
submodules: Split out GFX and all its dependencies into a servo-gfx submodule
This commit is contained in:
parent
6d4cb4319d
commit
a38887194f
61 changed files with 177 additions and 5720 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -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
2
configure
vendored
|
@ -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
|
||||
|
|
16
mk/sub.mk
16
mk/sub.mk
|
@ -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
1
src/servo-gfx
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 08d11a2db06e733ce9c84524819d37486a26c160
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
Binary file not shown.
|
@ -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.
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
|
@ -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." }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
pub struct FontconfigFontContextHandle {
|
||||
ctx: (),
|
||||
|
||||
drop { }
|
||||
}
|
||||
|
||||
pub impl FontconfigFontContextHandle {
|
||||
// this is a placeholder.
|
||||
static pub fn new() -> FontconfigFontContextHandle {
|
||||
FontconfigFontContextHandle { ctx: () }
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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: () }
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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) };
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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 |
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,8 +394,8 @@ 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()},
|
||||
layout_ctx.image_cache);
|
||||
let holder = ImageHolder::new({copy *d.image.get_ref()},
|
||||
layout_ctx.image_cache);
|
||||
|
||||
@ImageBox(RenderBoxData(node, ctx, self.next_box_id()), move holder)
|
||||
} else {
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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, © 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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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,18 +42,20 @@ 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| {
|
||||
do platform::runmain {
|
||||
#debug("preparing to enter main loop");
|
||||
OSMain {
|
||||
chan: do on_osmain::<Msg> |po, move dom_event_chan, move opts| {
|
||||
do platform::runmain {
|
||||
#debug("preparing to enter main loop");
|
||||
|
||||
// FIXME: Use the servo options.
|
||||
let mode;
|
||||
match os::getenv("SERVO_SHARE") {
|
||||
Some(_) => mode = ShareMode,
|
||||
None => mode = GlutMode
|
||||
}
|
||||
// FIXME: Use the servo options.
|
||||
let mode;
|
||||
match os::getenv("SERVO_SHARE") {
|
||||
Some(_) => mode = ShareMode,
|
||||
None => mode = GlutMode
|
||||
}
|
||||
|
||||
mainloop(mode, po, dom_event_chan.take(), &opts);
|
||||
mainloop(mode, po, dom_event_chan.take(), &opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
|
@ -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 => {}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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(~"", ~[]);
|
||||
}
|
||||
|
||||
*/
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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>()
|
||||
}
|
|
@ -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]);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue