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"]
|
[submodule "src/skia"]
|
||||||
path = src/skia
|
path = src/skia
|
||||||
url = git://github.com/mozilla-servo/skia.git
|
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 $?
|
(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" ]
|
if [ $CFG_OSTYPE = "darwin" ]
|
||||||
then
|
then
|
||||||
|
|
16
mk/sub.mk
16
mk/sub.mk
|
@ -81,6 +81,16 @@ DEPS_libcss += \
|
||||||
libparserutils \
|
libparserutils \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
|
DEPS_servo-gfx += \
|
||||||
|
rust-azure \
|
||||||
|
rust-cairo \
|
||||||
|
rust-freetype \
|
||||||
|
rust-geom \
|
||||||
|
rust-harfbuzz \
|
||||||
|
rust-http-client \
|
||||||
|
rust-stb-image \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
# Platform-specific dependencies
|
# Platform-specific dependencies
|
||||||
ifeq ($(CFG_OSTYPE),darwin)
|
ifeq ($(CFG_OSTYPE),darwin)
|
||||||
DEPS_rust-azure += \
|
DEPS_rust-azure += \
|
||||||
|
@ -119,6 +129,12 @@ DEPS_rust-layers += \
|
||||||
rust-core-text \
|
rust-core-text \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
|
DEPS_servo-gfx += \
|
||||||
|
rust-core-foundation \
|
||||||
|
rust-core-graphics \
|
||||||
|
rust-core-text \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(CFG_OSTYPE),linux)
|
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.
|
tasks.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export Content, ContentTask;
|
use dom::bindings::utils::rust_box;
|
||||||
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::document::Document;
|
use dom::document::Document;
|
||||||
use dom::node::{Node, NodeScope, define_bindings};
|
use dom::node::{Node, NodeScope, define_bindings};
|
||||||
use dom::event::{Event, ResizeEvent, ReflowEvent};
|
use dom::event::{Event, ResizeEvent, ReflowEvent};
|
||||||
use dom::window::Window;
|
use dom::window::Window;
|
||||||
use geom::size::Size2D;
|
|
||||||
use layout::layout_task;
|
use layout::layout_task;
|
||||||
use layout_task::{LayoutTask, BuildMsg, BuildData, AddStylesheet};
|
use layout::layout_task::{AddStylesheet, BuildData, BuildMsg, LayoutTask};
|
||||||
use resource::image_cache_task::ImageCacheTask;
|
|
||||||
|
|
||||||
use newcss::stylesheet::Stylesheet;
|
use core::comm::{Port, Chan, listen, select2};
|
||||||
|
use core::either;
|
||||||
use jsrt = js::rust::rt;
|
use core::task::{SingleThreaded, spawn, spawn_listener, task};
|
||||||
use js::rust::{cx, methods};
|
use core::io::{println, read_whole_file};
|
||||||
use js::global::{global_class, debug_fns};
|
use core::ptr::null;
|
||||||
|
use core::util::replace;
|
||||||
use either::{Either, Left, Right};
|
use geom::size::Size2D;
|
||||||
|
use gfx::resource::image_cache_task::ImageCacheTask;
|
||||||
use dom::bindings::utils::rust_box;
|
use gfx::resource::resource_task::ResourceTask;
|
||||||
use js::rust::compartment;
|
use gfx::util::url::make_url;
|
||||||
|
|
||||||
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 js::JSVAL_NULL;
|
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::{JSContext, JSVal};
|
||||||
use js::jsapi::bindgen::{JS_CallFunctionValue, JS_GetContextPrivate};
|
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 {
|
pub enum ControlMsg {
|
||||||
ParseMsg(Url),
|
ParseMsg(Url),
|
||||||
|
@ -89,7 +74,7 @@ fn ContentTask(layout_task: LayoutTask,
|
||||||
return move control_chan;
|
return move control_chan;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Content {
|
pub struct Content {
|
||||||
layout_task: LayoutTask,
|
layout_task: LayoutTask,
|
||||||
mut layout_join_port: Option<pipes::Port<()>>,
|
mut layout_join_port: Option<pipes::Port<()>>,
|
||||||
|
|
||||||
|
@ -159,7 +144,7 @@ fn Content(layout_task: LayoutTask,
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
fn task_from_context(cx: *JSContext) -> *Content unsafe {
|
pub fn task_from_context(cx: *JSContext) -> *Content unsafe {
|
||||||
cast::reinterpret_cast(&JS_GetContextPrivate(cx))
|
cast::reinterpret_cast(&JS_GetContextPrivate(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
use content::content_task::{ContentTask, ExecuteMsg, ParseMsg, ExitMsg};
|
use content::content_task::{ContentTask, ExecuteMsg, ParseMsg, ExitMsg};
|
||||||
use content::content_task;
|
use content::content_task;
|
||||||
use dom::event::Event;
|
use dom::event::Event;
|
||||||
use gfx::compositor::Compositor;
|
|
||||||
use gfx::render_task::RenderTask;
|
|
||||||
use gfx::render_task;
|
|
||||||
use layout::layout_task;
|
use layout::layout_task;
|
||||||
use layout_task::LayoutTask;
|
use layout_task::LayoutTask;
|
||||||
use opts::Opts;
|
|
||||||
use resource::image_cache_task::{ImageCacheTask, ImageCacheTaskClient};
|
use resource::image_cache_task::{ImageCacheTask, ImageCacheTaskClient};
|
||||||
use resource::resource_task::ResourceTask;
|
use resource::resource_task::ResourceTask;
|
||||||
use resource::resource_task;
|
use resource::resource_task;
|
||||||
|
|
||||||
use core::pipes::{Port, Chan};
|
use core::pipes::{Port, Chan};
|
||||||
use core::task::spawn_listener;
|
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::cell::Cell;
|
||||||
use std::net::url::Url;
|
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::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use gfx::display_list::{DisplayList, DisplayListBuilder};
|
use gfx::display_list::DisplayList;
|
||||||
use gfx::geometry::Au;
|
use gfx::geometry::Au;
|
||||||
use layout::box::{RenderBox};
|
use layout::box::{RenderBox};
|
||||||
use layout::context::LayoutContext;
|
use layout::context::LayoutContext;
|
||||||
|
use layout::display_list_builder::DisplayListBuilder;
|
||||||
use layout::flow::{FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow};
|
use layout::flow::{FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow};
|
||||||
use util::tree;
|
use util::tree;
|
||||||
|
|
||||||
|
|
|
@ -1,39 +1,34 @@
|
||||||
/* Fundamental layout structures and algorithms. */
|
/* Fundamental layout structures and algorithms. */
|
||||||
|
|
||||||
use geom::{Rect, Size2D, Point2D};
|
|
||||||
|
|
||||||
use dom::element::{ElementKind, HTMLDivElement, HTMLImageElement};
|
use dom::element::{ElementKind, HTMLDivElement, HTMLImageElement};
|
||||||
use dom::node::{Element, Node, NodeData, NodeKind, NodeTree};
|
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::context::LayoutContext;
|
||||||
use layout::debug::BoxedDebugMethods;
|
use layout::debug::BoxedDebugMethods;
|
||||||
use layout::display_list_builder::DisplayListBuilder;
|
use layout::display_list_builder::DisplayListBuilder;
|
||||||
use layout::flow::FlowContext;
|
use layout::flow::FlowContext;
|
||||||
use layout::text::TextBoxData;
|
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 util::tree::ReadMethods;
|
||||||
|
|
||||||
use arc = std::arc;
|
use arc = std::arc;
|
||||||
use arc::ARC;
|
|
||||||
use core::dvec::DVec;
|
use core::dvec::DVec;
|
||||||
use core::to_str::ToStr;
|
use core::to_str::ToStr;
|
||||||
use core::rand;
|
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 std::net::url::Url;
|
||||||
use task::spawn;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Render boxes (`struct RenderBox`) are the leafs of the layout
|
Render boxes (`struct RenderBox`) are the leafs of the layout
|
||||||
|
@ -131,7 +126,7 @@ fn RenderBoxData(node: Node, ctx: @FlowContext, id: int) -> RenderBoxData {
|
||||||
RenderBoxData {
|
RenderBoxData {
|
||||||
node : node,
|
node : node,
|
||||||
mut ctx : ctx,
|
mut ctx : ctx,
|
||||||
mut position : au::zero_rect(),
|
mut position : Au::zero_rect(),
|
||||||
font_size: Px(0.0),
|
font_size: Px(0.0),
|
||||||
id : id
|
id : id
|
||||||
}
|
}
|
||||||
|
@ -187,7 +182,7 @@ impl RenderBox : RenderBoxMethods {
|
||||||
|
|
||||||
let mut pieces_processed_count : uint = 0;
|
let mut pieces_processed_count : uint = 0;
|
||||||
let mut remaining_width : Au = max_width;
|
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;
|
let mut right_range : Option<Range> = None;
|
||||||
debug!("split_to_width: splitting text box (strlen=%u, range=%?, avail_width=%?)",
|
debug!("split_to_width: splitting text box (strlen=%u, range=%?, avail_width=%?)",
|
||||||
data.run.text.len(), data.range, max_width);
|
data.run.text.len(), data.range, max_width);
|
||||||
|
@ -264,7 +259,7 @@ impl RenderBox : RenderBoxMethods {
|
||||||
GenericBox(*) => Au(0),
|
GenericBox(*) => Au(0),
|
||||||
// TODO: consult CSS 'width', margin, border.
|
// TODO: consult CSS 'width', margin, border.
|
||||||
// TODO: If image isn't available, consult 'width'.
|
// 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),
|
TextBox(_,d) => d.run.min_width_for_range(d.range),
|
||||||
UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here."
|
UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here."
|
||||||
}
|
}
|
||||||
|
@ -278,7 +273,7 @@ impl RenderBox : RenderBoxMethods {
|
||||||
// FlowContext will combine the width of this element and
|
// FlowContext will combine the width of this element and
|
||||||
// that of its children to arrive at the context width.
|
// that of its children to arrive at the context width.
|
||||||
GenericBox(*) => Au(0),
|
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.
|
// 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| {
|
for d.run.glyphs.iter_glyphs_for_range(line_range) |_char_i, glyph| {
|
||||||
line_width += glyph.advance()
|
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
|
max_line_width
|
||||||
|
@ -328,8 +323,8 @@ impl RenderBox : RenderBoxMethods {
|
||||||
let size = i.size();
|
let size = i.size();
|
||||||
Rect {
|
Rect {
|
||||||
origin: copy self.d().position.origin,
|
origin: copy self.d().position.origin,
|
||||||
size: Size2D(au::from_px(size.width),
|
size: Size2D(Au::from_px(size.width),
|
||||||
au::from_px(size.height))
|
Au::from_px(size.height))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
GenericBox(*) => {
|
GenericBox(*) => {
|
||||||
|
@ -421,7 +416,7 @@ impl RenderBox : RenderBoxMethods {
|
||||||
// debug frames for text box bounds
|
// debug frames for text box bounds
|
||||||
debug!("%?", {
|
debug!("%?", {
|
||||||
list.append_item(~DisplayItem::new_Border(&abs_box_bounds,
|
list.append_item(~DisplayItem::new_Border(&abs_box_bounds,
|
||||||
au::from_px(1),
|
Au::from_px(1),
|
||||||
rgb(0, 0, 200).to_gfx_color()))
|
rgb(0, 0, 200).to_gfx_color()))
|
||||||
; ()});
|
; ()});
|
||||||
},
|
},
|
||||||
|
@ -469,10 +464,10 @@ impl RenderBox : RenderBoxMethods {
|
||||||
CSSBorderWidthLength(Px(right)),
|
CSSBorderWidthLength(Px(right)),
|
||||||
CSSBorderWidthLength(Px(bottom)),
|
CSSBorderWidthLength(Px(bottom)),
|
||||||
CSSBorderWidthLength(Px(left))) => {
|
CSSBorderWidthLength(Px(left))) => {
|
||||||
let top_au = au::from_frac_px(top);
|
let top_au = Au::from_frac_px(top);
|
||||||
let right_au = au::from_frac_px(right);
|
let right_au = Au::from_frac_px(right);
|
||||||
let bottom_au = au::from_frac_px(bottom);
|
let bottom_au = Au::from_frac_px(bottom);
|
||||||
let left_au = au::from_frac_px(left);
|
let left_au = Au::from_frac_px(left);
|
||||||
|
|
||||||
let all_widths_equal = [top_au, right_au, bottom_au].all(|a| *a == left_au);
|
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. */
|
/** 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::element::*;
|
||||||
use dom::node::{Comment, Doctype, Element, Text, Node, LayoutData};
|
use dom::node::{Comment, Doctype, Element, Text, Node, LayoutData};
|
||||||
use image::holder::ImageHolder;
|
|
||||||
use layout::box::*;
|
use layout::box::*;
|
||||||
use layout::block::BlockFlowData;
|
use layout::block::BlockFlowData;
|
||||||
use layout::context::LayoutContext;
|
use layout::context::LayoutContext;
|
||||||
use layout::flow::*;
|
use layout::flow::*;
|
||||||
use layout::inline::InlineFlowData;
|
use layout::inline::InlineFlowData;
|
||||||
use layout::root::RootFlowData;
|
use layout::root::RootFlowData;
|
||||||
use option::is_none;
|
|
||||||
use util::tree;
|
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 {
|
pub struct LayoutTreeBuilder {
|
||||||
mut root_flow: Option<@FlowContext>,
|
mut root_flow: Option<@FlowContext>,
|
||||||
mut next_bid: int,
|
mut next_bid: int,
|
||||||
|
@ -150,7 +151,7 @@ impl BoxGenerator {
|
||||||
self.flow.inline().boxes.push(*spacer);
|
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());
|
node_range.extend_to(self.flow.inline().boxes.len());
|
||||||
assert node_range.length() > 0;
|
assert node_range.length() > 0;
|
||||||
|
|
||||||
|
@ -393,8 +394,8 @@ impl LayoutTreeBuilder {
|
||||||
// TODO: this could be written as a pattern guard, but it triggers
|
// TODO: this could be written as a pattern guard, but it triggers
|
||||||
// an ICE (mozilla/rust issue #3601)
|
// an ICE (mozilla/rust issue #3601)
|
||||||
if d.image.is_some() {
|
if d.image.is_some() {
|
||||||
let holder = ImageHolder({copy *d.image.get_ref()},
|
let holder = ImageHolder::new({copy *d.image.get_ref()},
|
||||||
layout_ctx.image_cache);
|
layout_ctx.image_cache);
|
||||||
|
|
||||||
@ImageBox(RenderBoxData(node, ctx, self.next_box_id()), move holder)
|
@ImageBox(RenderBoxData(node, ctx, self.next_box_id()), move holder)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use gfx::{
|
use gfx::font_context::FontContext;
|
||||||
Au,
|
use gfx::geometry::Au;
|
||||||
FontContext,
|
use gfx::resource::local_image_cache::LocalImageCache;
|
||||||
};
|
|
||||||
use resource::local_image_cache::LocalImageCache;
|
|
||||||
use std::net::url::Url;
|
use std::net::url::Url;
|
||||||
|
|
||||||
/* Represents layout task context. */
|
/* Represents layout task context. */
|
||||||
|
|
|
@ -11,7 +11,6 @@ use either::{Left, Right};
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::size::Size2D;
|
use geom::size::Size2D;
|
||||||
use gfx::display_list::DisplayList;
|
|
||||||
use layout::box::{RenderBox, TextBox};
|
use layout::box::{RenderBox, TextBox};
|
||||||
use layout::context::LayoutContext;
|
use layout::context::LayoutContext;
|
||||||
use layout::flow::FlowContext;
|
use layout::flow::FlowContext;
|
||||||
|
@ -19,6 +18,8 @@ use layout::text::TextBoxData;
|
||||||
use util::tree;
|
use util::tree;
|
||||||
use vec::push;
|
use vec::push;
|
||||||
|
|
||||||
|
use gfx::display_list::DisplayList;
|
||||||
|
|
||||||
/** A builder object that manages display list builder should mainly
|
/** A builder object that manages display list builder should mainly
|
||||||
hold information about the initial request and desired result---for
|
hold information about the initial request and desired result---for
|
||||||
example, whether the DisplayList to be used for painting or hit
|
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 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::block::BlockFlowData;
|
||||||
use layout::box::RenderBox;
|
use layout::box::RenderBox;
|
||||||
use layout::context::LayoutContext;
|
use layout::context::LayoutContext;
|
||||||
use layout::debug::BoxedDebugMethods;
|
use layout::debug::BoxedDebugMethods;
|
||||||
|
use layout::display_list_builder::DisplayListBuilder;
|
||||||
use layout::inline::{InlineFlowData, NodeRange};
|
use layout::inline::{InlineFlowData, NodeRange};
|
||||||
use layout::root::RootFlowData;
|
use layout::root::RootFlowData;
|
||||||
use util::range::{Range, MutableRange};
|
|
||||||
use util::tree;
|
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
|
/** Servo's experimental layout system builds a tree of FlowContexts
|
||||||
and RenderBoxes, and figures out positions and display attributes of
|
and RenderBoxes, and figures out positions and display attributes of
|
||||||
tree nodes. Positions are computed in several tree traversals driven
|
tree nodes. Positions are computed in several tree traversals driven
|
||||||
|
@ -103,7 +103,7 @@ fn FlowData(id: int) -> FlowData {
|
||||||
|
|
||||||
min_width: Au(0),
|
min_width: Au(0),
|
||||||
pref_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 dom::node::Node;
|
||||||
use gfx::dl;
|
|
||||||
use gfx::{
|
|
||||||
Au,
|
|
||||||
FontStyle,
|
|
||||||
};
|
|
||||||
use layout::box::*;
|
use layout::box::*;
|
||||||
use layout::context::LayoutContext;
|
use layout::context::LayoutContext;
|
||||||
use layout::flow::{FlowContext, InlineFlow};
|
use layout::flow::{FlowContext, InlineFlow};
|
||||||
use layout::text::TextBoxData;
|
use layout::text::TextBoxData;
|
||||||
use newcss::values::{BoxAuto, BoxLength, Px};
|
|
||||||
use servo_text::util::*;
|
|
||||||
use util::range::{MutableRange, Range};
|
|
||||||
use util::tree;
|
use util::tree;
|
||||||
|
|
||||||
use core::dlist::DList;
|
use core::dlist::DList;
|
||||||
use core::dvec::DVec;
|
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;
|
use std::arc;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -158,7 +153,7 @@ struct TextRunScanner {
|
||||||
|
|
||||||
fn TextRunScanner(flow: @FlowContext) -> TextRunScanner {
|
fn TextRunScanner(flow: @FlowContext) -> TextRunScanner {
|
||||||
TextRunScanner {
|
TextRunScanner {
|
||||||
clump: util::range::empty_mut(),
|
clump: MutableRange::empty(),
|
||||||
flow: flow,
|
flow: flow,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,7 +328,7 @@ fn LineboxScanner(inline: @FlowContext) -> LineboxScanner {
|
||||||
flow: inline,
|
flow: inline,
|
||||||
new_boxes: DVec(),
|
new_boxes: DVec(),
|
||||||
work_list: DList(),
|
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()
|
line_spans: DVec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -567,8 +562,8 @@ impl FlowContext : InlineLayout {
|
||||||
|
|
||||||
for self.inline().boxes.each |box| {
|
for self.inline().boxes.each |box| {
|
||||||
debug!("FlowContext[%d]: measuring %s", self.d().id, box.debug_str());
|
debug!("FlowContext[%d]: measuring %s", self.d().id, box.debug_str());
|
||||||
min_width = au::max(min_width, box.get_min_width(ctx));
|
min_width = Au::max(min_width, box.get_min_width(ctx));
|
||||||
pref_width = au::max(pref_width, box.get_pref_width(ctx));
|
pref_width = Au::max(pref_width, box.get_pref_width(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.d().min_width = min_width;
|
self.d().min_width = min_width;
|
||||||
|
@ -586,11 +581,11 @@ impl FlowContext : InlineLayout {
|
||||||
// over the box list, and/or put into RenderBox.
|
// over the box list, and/or put into RenderBox.
|
||||||
for self.inline().boxes.each |box| {
|
for self.inline().boxes.each |box| {
|
||||||
box.d().position.size.width = match *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 */
|
@TextBox(*) => { /* text boxes are initialized with dimensions */
|
||||||
box.d().position.size.width
|
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)
|
_ => fail fmt!("Tried to assign width to unknown Box variant: %?", box)
|
||||||
};
|
};
|
||||||
} // for boxes.each |box|
|
} // for boxes.each |box|
|
||||||
|
@ -609,24 +604,24 @@ impl FlowContext : InlineLayout {
|
||||||
|
|
||||||
fn assign_height_inline(@self, _ctx: &LayoutContext) {
|
fn assign_height_inline(@self, _ctx: &LayoutContext) {
|
||||||
// TODO: get from CSS 'line-height' property
|
// 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);
|
let mut cur_y = Au(0);
|
||||||
|
|
||||||
for self.inline().lines.eachi |i, line_span| {
|
for self.inline().lines.eachi |i, line_span| {
|
||||||
debug!("assign_height_inline: processing line %u with box span: %?", i, line_span);
|
debug!("assign_height_inline: processing line %u with box span: %?", i, line_span);
|
||||||
// coords relative to left baseline
|
// 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;
|
let boxes = &self.inline().boxes;
|
||||||
for line_span.eachi |box_i| {
|
for line_span.eachi |box_i| {
|
||||||
let cur_box = boxes[box_i];
|
let cur_box = boxes[box_i];
|
||||||
|
|
||||||
// compute box height.
|
// compute box height.
|
||||||
cur_box.d().position.size.height = match cur_box {
|
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 */
|
@TextBox(*) => { /* text boxes are initialized with dimensions */
|
||||||
cur_box.d().position.size.height
|
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())
|
_ => 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);
|
debug!("assign_height_inline: linebox bounding box = %?", linebox_bounding_box);
|
||||||
}
|
}
|
||||||
let linebox_height = linebox_bounding_box.size.height;
|
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|
|
} // /lines.each |line_span|
|
||||||
|
|
||||||
self.d().position.size.height = cur_y;
|
self.d().position.size.height = cur_y;
|
||||||
|
|
|
@ -7,21 +7,16 @@ use content::content_task;
|
||||||
use css::select::new_css_select_ctx;
|
use css::select::new_css_select_ctx;
|
||||||
use dom::event::{Event, ReflowEvent};
|
use dom::event::{Event, ReflowEvent};
|
||||||
use dom::node::{Node, LayoutData};
|
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::RenderBox;
|
||||||
use layout::box_builder::LayoutTreeBuilder;
|
use layout::box_builder::LayoutTreeBuilder;
|
||||||
use layout::context::LayoutContext;
|
use layout::context::LayoutContext;
|
||||||
|
use layout::display_list_builder::DisplayListBuilder;
|
||||||
use layout::traverse::*;
|
use layout::traverse::*;
|
||||||
use opt = core::option;
|
|
||||||
use opts::Opts;
|
|
||||||
use render_task::RenderTask;
|
|
||||||
use resource::image_cache_task::{ImageCacheTask, ImageResponseMsg};
|
use resource::image_cache_task::{ImageCacheTask, ImageResponseMsg};
|
||||||
use resource::local_image_cache::LocalImageCache;
|
use resource::local_image_cache::LocalImageCache;
|
||||||
use util::time::time;
|
use util::time::time;
|
||||||
|
|
||||||
use core::comm::*;
|
use core::comm::*; // FIXME: Bad! Pipe-ify me.
|
||||||
use core::dvec::DVec;
|
use core::dvec::DVec;
|
||||||
use core::mutable::Mut;
|
use core::mutable::Mut;
|
||||||
use core::task::*;
|
use core::task::*;
|
||||||
|
@ -29,9 +24,16 @@ use core::util::replace;
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::size::Size2D;
|
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::select::SelectCtx;
|
||||||
use newcss::stylesheet::Stylesheet;
|
use newcss::stylesheet::Stylesheet;
|
||||||
use newcss::types::OriginAuthor;
|
use newcss::types::OriginAuthor;
|
||||||
|
use opt = core::option;
|
||||||
use std::arc::ARC;
|
use std::arc::ARC;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::net::url::Url;
|
use std::net::url::Url;
|
||||||
|
@ -159,8 +161,8 @@ impl Layout {
|
||||||
// Reset the image cache
|
// Reset the image cache
|
||||||
self.local_image_cache.next_round(self.make_on_image_available_cb(move dom_event_chan));
|
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),
|
let screen_size = Size2D(Au::from_px(data.window_size.width as int),
|
||||||
au::from_px(data.window_size.height as int));
|
Au::from_px(data.window_size.height as int));
|
||||||
|
|
||||||
let layout_ctx = LayoutContext {
|
let layout_ctx = LayoutContext {
|
||||||
image_cache: self.local_image_cache,
|
image_cache: self.local_image_cache,
|
||||||
|
@ -202,20 +204,20 @@ impl Layout {
|
||||||
}
|
}
|
||||||
|
|
||||||
do time("layout: display list building") {
|
do time("layout: display list building") {
|
||||||
let builder = dl::DisplayListBuilder {
|
let builder = DisplayListBuilder {
|
||||||
ctx: &layout_ctx,
|
ctx: &layout_ctx,
|
||||||
};
|
};
|
||||||
let mut render_layer = RenderLayer {
|
let mut render_layer = RenderLayer {
|
||||||
display_list: DisplayList::new(),
|
display_list: DisplayList::new(),
|
||||||
size: Size2D(au::to_px(screen_size.width) as uint,
|
size: Size2D(screen_size.width.to_px() as uint,
|
||||||
au::to_px(screen_size.height) as uint)
|
screen_size.height.to_px() as uint)
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: set options on the builder before building
|
// TODO: set options on the builder before building
|
||||||
// TODO: be smarter about what needs painting
|
// TODO: be smarter about what needs painting
|
||||||
layout_root.build_display_list(&builder, © layout_root.d().position,
|
layout_root.build_display_list(&builder, © layout_root.d().position,
|
||||||
&mut render_layer.display_list);
|
&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)
|
} // time(layout: display list building)
|
||||||
|
|
||||||
// Tell content we're done
|
// Tell content we're done
|
||||||
|
@ -242,8 +244,8 @@ impl Layout {
|
||||||
match rect {
|
match rect {
|
||||||
None => Err(()),
|
None => Err(()),
|
||||||
Some(rect) => {
|
Some(rect) => {
|
||||||
let size = Size2D(au::to_px(rect.size.width),
|
let size = Size2D(rect.size.width.to_px(),
|
||||||
au::to_px(rect.size.height));
|
rect.size.height.to_px());
|
||||||
Ok(ContentSize(move size))
|
Ok(ContentSize(move size))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@ use au = gfx::geometry;
|
||||||
use newcss::values::*;
|
use newcss::values::*;
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use gfx::display_list::{DisplayList, DisplayListBuilder};
|
use gfx::display_list::DisplayList;
|
||||||
use gfx::geometry::Au;
|
use gfx::geometry::Au;
|
||||||
use layout::box::RenderBox;
|
use layout::box::RenderBox;
|
||||||
use layout::context::LayoutContext;
|
use layout::context::LayoutContext;
|
||||||
use layout::flow::{FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow};
|
use layout::flow::{FlowContext, FlowTree, InlineBlockFlow, BlockFlow, RootFlow};
|
||||||
|
use layout::display_list_builder::DisplayListBuilder;
|
||||||
use util::tree;
|
use util::tree;
|
||||||
|
|
||||||
struct RootFlowData {
|
struct RootFlowData {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
/** Text layout. */
|
/** Text layout. */
|
||||||
|
|
||||||
use servo_text::TextRun;
|
|
||||||
use layout::box::{TextBox, RenderBox, RenderBoxData, UnscannedTextBox};
|
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 {
|
pub struct TextBoxData {
|
||||||
run: @TextRun,
|
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 ShareGlContext = sharegl::platform::Context;
|
||||||
use dom::event::{Event, ResizeEvent};
|
use dom::event::{Event, ResizeEvent};
|
||||||
use gfx::compositor::{Compositor, LayerBuffer, LayerBufferSet};
|
|
||||||
use layers::ImageLayer;
|
|
||||||
use opts::Opts;
|
|
||||||
use resize_rate_limiter::ResizeRateLimiter;
|
use resize_rate_limiter::ResizeRateLimiter;
|
||||||
use util::time;
|
|
||||||
|
|
||||||
use azure::azure_hl::{BackendType, B8G8R8A8, DataSourceSurface, DrawTarget, SourceSurfaceMethods};
|
use azure::azure_hl::{BackendType, B8G8R8A8, DataSourceSurface, DrawTarget, SourceSurfaceMethods};
|
||||||
use core::dvec::DVec;
|
use core::dvec::DVec;
|
||||||
|
@ -15,10 +11,16 @@ use geom::matrix::{Matrix4, identity};
|
||||||
use geom::point::Point2D;
|
use geom::point::Point2D;
|
||||||
use geom::rect::Rect;
|
use geom::rect::Rect;
|
||||||
use geom::size::Size2D;
|
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::cell::Cell;
|
||||||
use std::cmp::FuzzyEq;
|
use std::cmp::FuzzyEq;
|
||||||
|
|
||||||
pub type OSMain = comm::Chan<Msg>;
|
pub struct OSMain {
|
||||||
|
chan: comm::Chan<Msg>
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: Move me over to opts.rs.
|
// FIXME: Move me over to opts.rs.
|
||||||
enum Mode {
|
enum Mode {
|
||||||
|
@ -40,18 +42,20 @@ pub enum Msg {
|
||||||
|
|
||||||
fn OSMain(dom_event_chan: pipes::SharedChan<Event>, opts: Opts) -> OSMain {
|
fn OSMain(dom_event_chan: pipes::SharedChan<Event>, opts: Opts) -> OSMain {
|
||||||
let dom_event_chan = Cell(move dom_event_chan);
|
let dom_event_chan = Cell(move dom_event_chan);
|
||||||
do on_osmain::<Msg> |po, move dom_event_chan, move opts| {
|
OSMain {
|
||||||
do platform::runmain {
|
chan: do on_osmain::<Msg> |po, move dom_event_chan, move opts| {
|
||||||
#debug("preparing to enter main loop");
|
do platform::runmain {
|
||||||
|
#debug("preparing to enter main loop");
|
||||||
|
|
||||||
// FIXME: Use the servo options.
|
// FIXME: Use the servo options.
|
||||||
let mode;
|
let mode;
|
||||||
match os::getenv("SERVO_SHARE") {
|
match os::getenv("SERVO_SHARE") {
|
||||||
Some(_) => mode = ShareMode,
|
Some(_) => mode = ShareMode,
|
||||||
None => mode = GlutMode
|
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 {
|
impl OSMain : Compositor {
|
||||||
fn begin_drawing(next_dt: pipes::Chan<LayerBufferSet>) {
|
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) {
|
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 azure;
|
||||||
extern mod cairo;
|
extern mod cairo;
|
||||||
extern mod geom;
|
extern mod geom;
|
||||||
|
extern mod gfx (name = "servo_gfx");
|
||||||
extern mod glut;
|
extern mod glut;
|
||||||
extern mod http_client;
|
extern mod http_client;
|
||||||
extern mod hubbub;
|
extern mod hubbub;
|
||||||
|
@ -69,54 +70,6 @@ pub mod layout {
|
||||||
mod aux;
|
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 html {
|
||||||
pub mod cssparse;
|
pub mod cssparse;
|
||||||
pub mod hubbub_html_parser;
|
pub mod hubbub_html_parser;
|
||||||
|
@ -128,37 +81,9 @@ pub mod platform {
|
||||||
priv mod resize_rate_limiter;
|
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 util {
|
||||||
pub mod actor;
|
pub mod actor;
|
||||||
pub mod cache;
|
|
||||||
pub mod range;
|
|
||||||
pub mod time;
|
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
pub mod url;
|
|
||||||
pub mod vec;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod opts;
|
|
||||||
|
|
||||||
use servo_util = util;
|
use servo_util = util;
|
||||||
use servo_text = text;
|
|
||||||
|
|
|
@ -3,22 +3,23 @@ extern mod core_graphics;
|
||||||
#[cfg(target_os="macos")]
|
#[cfg(target_os="macos")]
|
||||||
extern mod core_text;
|
extern mod core_text;
|
||||||
|
|
||||||
use comm::*;
|
use engine::{Engine, ExitMsg, LoadURLMsg}; // FIXME: "ExitMsg" is pollution.
|
||||||
use option::swap_unwrap;
|
use platform::osmain::{AddKeyHandler, OSMain};
|
||||||
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 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() {
|
fn main() {
|
||||||
let args = os::args();
|
let args = os::args();
|
||||||
run(&opts::from_cmdline_args(args))
|
run(&gfx::opts::from_cmdline_args(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(non_implicitly_copyable_typarams)]
|
#[allow(non_implicitly_copyable_typarams)]
|
||||||
|
@ -44,7 +45,7 @@ fn run_pipeline_screen(opts: &Opts) {
|
||||||
|
|
||||||
// Send each file to render then wait for keypress
|
// Send each file to render then wait for keypress
|
||||||
let (keypress_to_engine, keypress_from_osmain) = pipes::stream();
|
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
|
// Create a servo instance
|
||||||
let resource_task = ResourceTask();
|
let resource_task = ResourceTask();
|
||||||
|
@ -70,7 +71,7 @@ fn run_pipeline_screen(opts: &Opts) {
|
||||||
engine_task.send(engine::ExitMsg(move exit_chan));
|
engine_task.send(engine::ExitMsg(move exit_chan));
|
||||||
exit_response_from_engine.recv();
|
exit_response_from_engine.recv();
|
||||||
|
|
||||||
osmain.send(osmain::Exit);
|
osmain.chan.send(platform::osmain::Exit);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_pipeline_png(_opts: &Opts, _outfile: &str) {
|
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