mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
Remove the servo-gfx submodule
This commit is contained in:
parent
5e70e3b153
commit
463b80090c
48 changed files with 5536 additions and 26 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -85,6 +85,3 @@
|
||||||
[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
|
|
||||||
|
|
21
Makefile.in
21
Makefile.in
|
@ -130,12 +130,20 @@ endef
|
||||||
$(foreach submodule,$(CFG_SUBMODULES),\
|
$(foreach submodule,$(CFG_SUBMODULES),\
|
||||||
$(eval $(call DEF_SUBMODULE_RULES,$(submodule))))
|
$(eval $(call DEF_SUBMODULE_RULES,$(submodule))))
|
||||||
|
|
||||||
RFLAGS_servo = $(strip $(CFG_RUSTC_FLAGS)) $(addprefix -L $(B)src/,$(DEPS_SUBMODULES))
|
|
||||||
SRC_servo = $(call rwildcard,$(S)src/servo/,*.rs)
|
|
||||||
CRATE_servo = $(S)src/servo/servo.rc
|
|
||||||
DONE_SUBMODULES = $(foreach dep,$(DEPS_SUBMODULES),$(DONE_$(dep)))
|
DONE_SUBMODULES = $(foreach dep,$(DEPS_SUBMODULES),$(DONE_$(dep)))
|
||||||
|
|
||||||
DEPS_servo = $(CRATE_servo) $(SRC_servo) $(DONE_SUBMODULES)
|
RFLAGS_servo_gfx = $(strip $(CFG_RUSTC_FLAGS)) $(addprefix -L $(B)src/,$(DEPS_SUBMODULES))
|
||||||
|
SRC_servo_gfx = $(call rwildcard,$(S)src/servo-gfx-2/,*.rs)
|
||||||
|
CRATE_servo_gfx = $(S)src/servo-gfx-2/servo_gfx.rc
|
||||||
|
DONE_servo_gfx = $(B)src/servo-gfx-2/libservogfx.dummy
|
||||||
|
|
||||||
|
DEPS_servo_gfx = $(CRATE_servo_gfx) $(SRC_servo_gfx) $(DONE_SUBMODULES)
|
||||||
|
|
||||||
|
RFLAGS_servo = $(strip $(CFG_RUSTC_FLAGS)) $(addprefix -L $(B)src/,$(DEPS_SUBMODULES)) -L $(B)src/servo-gfx-2
|
||||||
|
SRC_servo = $(call rwildcard,$(S)src/servo/,*.rs)
|
||||||
|
CRATE_servo = $(S)src/servo/servo.rc
|
||||||
|
|
||||||
|
DEPS_servo = $(CRATE_servo) $(SRC_servo) $(DONE_SUBMODULES) $(DONE_servo_gfx)
|
||||||
|
|
||||||
# rules that depend on having correct meta-target vars (DEPS_CLEAN, DEPS_servo, etc)
|
# rules that depend on having correct meta-target vars (DEPS_CLEAN, DEPS_servo, etc)
|
||||||
include $(S)mk/check.mk
|
include $(S)mk/check.mk
|
||||||
|
@ -145,6 +153,11 @@ include $(S)mk/clean.mk
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: servo package
|
all: servo package
|
||||||
|
|
||||||
|
# Servo helper libraries
|
||||||
|
|
||||||
|
$(DONE_servo_gfx): $(DEPS_servo_gfx)
|
||||||
|
$(RUSTC) $(RFLAGS_servo_gfx) -o $@ $< && touch $@
|
||||||
|
|
||||||
# Servo binaries
|
# Servo binaries
|
||||||
|
|
||||||
servo: $(DEPS_servo)
|
servo: $(DEPS_servo)
|
||||||
|
|
3
configure
vendored
3
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 servo-gfx"
|
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"
|
||||||
|
|
||||||
if [ $CFG_OSTYPE = "darwin" ]
|
if [ $CFG_OSTYPE = "darwin" ]
|
||||||
then
|
then
|
||||||
|
@ -365,6 +365,7 @@ do
|
||||||
make_dir ${CFG_BUILD_DIR}src/${i}
|
make_dir ${CFG_BUILD_DIR}src/${i}
|
||||||
done
|
done
|
||||||
|
|
||||||
|
make_dir ${CFG_BUILD_DIR}src/servo-gfx-2
|
||||||
make_dir src/test/ref
|
make_dir src/test/ref
|
||||||
|
|
||||||
# TODO: don't run configure on submodules unless necessary. For an example,
|
# TODO: don't run configure on submodules unless necessary. For an example,
|
||||||
|
|
18
mk/sub.mk
18
mk/sub.mk
|
@ -81,16 +81,6 @@ 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 += \
|
||||||
|
@ -105,7 +95,7 @@ DEPS_rust-cairo += \
|
||||||
rust-core-graphics \
|
rust-core-graphics \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
DEPS_rust-io-surface += \
|
dEPS_rust-io-surface += \
|
||||||
rust-core-foundation \
|
rust-core-foundation \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
|
@ -129,12 +119,6 @@ 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 +0,0 @@
|
||||||
Subproject commit 08d11a2db06e733ce9c84524819d37486a26c160
|
|
BIN
src/servo-gfx-2/JosefinSans-SemiBold.ttf
Normal file
BIN
src/servo-gfx-2/JosefinSans-SemiBold.ttf
Normal file
Binary file not shown.
93
src/servo-gfx-2/OFL.txt
Normal file
93
src/servo-gfx-2/OFL.txt
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
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.
|
18
src/servo-gfx-2/color.rs
Normal file
18
src/servo-gfx-2/color.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
28
src/servo-gfx-2/compositor.rs
Normal file
28
src/servo-gfx-2/compositor.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use azure::azure_hl::{DrawTarget};
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
117
src/servo-gfx-2/display_list.rs
Normal file
117
src/servo-gfx-2/display_list.rs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
use color::{Color, rgb};
|
||||||
|
use geometry::Au;
|
||||||
|
use image::base::Image;
|
||||||
|
use render_context::RenderContext;
|
||||||
|
use text::SendableTextRun;
|
||||||
|
use util::range::Range;
|
||||||
|
|
||||||
|
use azure::azure_hl::DrawTarget;
|
||||||
|
use core::dvec::DVec;
|
||||||
|
use clone_arc = std::arc::clone;
|
||||||
|
use geom::Rect;
|
||||||
|
use geom::Point2D;
|
||||||
|
use std::arc::ARC;
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
499
src/servo-gfx-2/font.rs
Normal file
499
src/servo-gfx-2/font.rs
Normal file
|
@ -0,0 +1,499 @@
|
||||||
|
use color::Color;
|
||||||
|
use geometry::Au;
|
||||||
|
use render_context::RenderContext;
|
||||||
|
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;
|
||||||
|
use geom::{Point2D, Rect, Size2D};
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
*/
|
||||||
|
pub 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());
|
||||||
|
|
||||||
|
for 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: (origin.x + glyph_offset.x).to_px() as AzFloat,
|
||||||
|
y: (origin.y + glyph_offset.y).to_px() 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);
|
||||||
|
|
||||||
|
debug!("measuring text range '%s'", run.text.substr(range.begin(), range.length()));
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
124
src/servo-gfx-2/font_context.rs
Normal file
124
src/servo-gfx-2/font_context.rs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
use font::{Font, FontDescriptor, FontGroup, FontStyle, SelectorPlatformName, SelectorStubDummy};
|
||||||
|
use font::{SpecifiedFontStyle};
|
||||||
|
use font_list::FontList;
|
||||||
|
use 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 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." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
137
src/servo-gfx-2/font_list.rs
Normal file
137
src/servo-gfx-2/font_list.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
use font::{CSSFontWeight, SpecifiedFontStyle};
|
||||||
|
use 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 }
|
||||||
|
}
|
12
src/servo-gfx-2/fontconfig/font_list.rs
Normal file
12
src/servo-gfx-2/fontconfig/font_list.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
pub struct FontconfigFontContextHandle {
|
||||||
|
ctx: (),
|
||||||
|
|
||||||
|
drop { }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub impl FontconfigFontContextHandle {
|
||||||
|
// this is a placeholder.
|
||||||
|
static pub fn new() -> FontconfigFontContextHandle {
|
||||||
|
FontconfigFontContextHandle { ctx: () }
|
||||||
|
}
|
||||||
|
}
|
151
src/servo-gfx-2/freetype/font.rs
Normal file
151
src/servo-gfx-2/freetype/font.rs
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
extern mod freetype;
|
||||||
|
|
||||||
|
use font::{FontMetrics, FractionalPixel};
|
||||||
|
use font_context::FreeTypeFontContext;
|
||||||
|
use geometry::Au;
|
||||||
|
use text::glyph::GlyphIndex;
|
||||||
|
use text::util::{float_to_fixed, fixed_to_float};
|
||||||
|
|
||||||
|
use cast::reinterpret_cast;
|
||||||
|
use ptr::{addr_of, null};
|
||||||
|
use vec_as_buf = vec::as_imm_buf;
|
||||||
|
|
||||||
|
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 }
|
||||||
|
}
|
33
src/servo-gfx-2/freetype/font_context.rs
Normal file
33
src/servo-gfx-2/freetype/font_context.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
109
src/servo-gfx-2/geometry.rs
Normal file
109
src/servo-gfx-2/geometry.rs
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
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 pure fn scale_by(factor: float) -> Au {
|
||||||
|
Au(((*self as float) * factor) as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
static pub pure fn from_px(i: int) -> Au {
|
||||||
|
from_int(i * 60)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub pure fn to_px(&const self) -> int {
|
||||||
|
(**self / 60) as int
|
||||||
|
}
|
||||||
|
|
||||||
|
static pub pure fn zero_point() -> Point2D<Au> {
|
||||||
|
Point2D(Au(0), Au(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
static pub pure fn zero_rect() -> Rect<Au> {
|
||||||
|
let z = Au(0);
|
||||||
|
Rect(Point2D(z, z), Size2D(z, z))
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes 72 points per inch, and 96 px per inch
|
||||||
|
static pub pure fn from_pt(f: float) -> Au {
|
||||||
|
from_px((f / 72f * 96f) as int)
|
||||||
|
}
|
||||||
|
|
||||||
|
static pub pure fn from_frac_px(f: float) -> Au {
|
||||||
|
Au((f * 60f) as i32)
|
||||||
|
}
|
||||||
|
|
||||||
|
static pub pure fn min(x: Au, y: Au) -> Au { if *x < *y { x } else { y } }
|
||||||
|
static pub pure fn max(x: Au, y: Au) -> Au { if *x > *y { x } else { y } }
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
43
src/servo-gfx-2/image/base.rs
Normal file
43
src/servo-gfx-2/image/base.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
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");
|
||||||
|
|
||||||
|
pub 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)
|
||||||
|
}
|
||||||
|
}
|
23
src/servo-gfx-2/image/encode/tga.rs
Normal file
23
src/servo-gfx-2/image/encode/tga.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use io::WriterUtil;
|
||||||
|
|
||||||
|
fn encode(writer: io::Writer, surface: &surface::image_surface) {
|
||||||
|
assert surface.format == 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);
|
||||||
|
}
|
||||||
|
|
102
src/servo-gfx-2/image/holder.rs
Normal file
102
src/servo-gfx-2/image/holder.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
use image::base::Image;
|
||||||
|
use resource::image_cache_task::{ImageCacheTask, ImageReady, ImageNotReady, ImageFailed};
|
||||||
|
use resource::image_cache_task;
|
||||||
|
use resource::local_image_cache::LocalImageCache;
|
||||||
|
|
||||||
|
use core::util::replace;
|
||||||
|
use geom::size::Size2D;
|
||||||
|
use std::net::url::Url;
|
||||||
|
use std::arc::{ARC, clone, get};
|
||||||
|
|
||||||
|
// FIXME: Nasty coupling to resource here. This should probably be factored out into an interface
|
||||||
|
// and use dependency injection.
|
||||||
|
|
||||||
|
/** 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageHolder {
|
||||||
|
static pub fn new(url: Url, local_image_cache: @LocalImageCache) -> ImageHolder {
|
||||||
|
debug!("ImageHolder::new() %?", 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
BIN
src/servo-gfx-2/image/test.jpeg
Normal file
BIN
src/servo-gfx-2/image/test.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
9
src/servo-gfx-2/native.rs
Normal file
9
src/servo-gfx-2/native.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/* 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_gfx.rc. This is not ideal and may be changed in the future. */
|
||||||
|
|
||||||
|
pub use font::FontHandle;
|
||||||
|
pub use font_context::FontContextHandle;
|
||||||
|
pub use font_list::FontListHandle;
|
77
src/servo-gfx-2/opts.rs
Normal file
77
src/servo-gfx-2/opts.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
//! 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,
|
||||||
|
}
|
||||||
|
}
|
161
src/servo-gfx-2/quartz/font.rs
Normal file
161
src/servo-gfx-2/quartz/font.rs
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
extern mod core_foundation;
|
||||||
|
extern mod core_graphics;
|
||||||
|
extern mod core_text;
|
||||||
|
|
||||||
|
use font_context::QuartzFontContextHandle;
|
||||||
|
use geometry::Au;
|
||||||
|
use servo_gfx_font::{CSSFontWeight, FontHandleMethods, FontMetrics, FontWeight100, FontWeight200};
|
||||||
|
use servo_gfx_font::{FontWeight300, FontWeight400, FontWeight500, FontWeight600, FontWeight700};
|
||||||
|
use servo_gfx_font::{FontWeight800, FontWeight900, FractionalPixel};
|
||||||
|
use text::glyph::GlyphIndex;
|
||||||
|
|
||||||
|
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 core::libc::size_t;
|
||||||
|
use ct = core_text;
|
||||||
|
use ct::font::CTFont;
|
||||||
|
use ct::font_descriptor::{kCTFontDefaultOrientation, CTFontSymbolicTraits};
|
||||||
|
use ct::font_descriptor::{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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
12
src/servo-gfx-2/quartz/font_context.rs
Normal file
12
src/servo-gfx-2/quartz/font_context.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
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: () }
|
||||||
|
}
|
||||||
|
}
|
56
src/servo-gfx-2/quartz/font_list.rs
Normal file
56
src/servo-gfx-2/quartz/font_list.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
extern mod core_foundation;
|
||||||
|
extern mod core_text;
|
||||||
|
|
||||||
|
use quartz::font::{QuartzFontHandle};
|
||||||
|
use servo_gfx_font::FontHandle;
|
||||||
|
use servo_gfx_font_list::{FontEntry, FontFamily};
|
||||||
|
|
||||||
|
use cf = core_foundation;
|
||||||
|
use cf::array::CFArray;
|
||||||
|
use core::dvec::DVec;
|
||||||
|
use core::send_map::{linear, SendMap};
|
||||||
|
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};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
105
src/servo-gfx-2/render_context.rs
Normal file
105
src/servo-gfx-2/render_context.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
use compositor::LayerBuffer;
|
||||||
|
use font_context::FontContext;
|
||||||
|
use geometry::Au;
|
||||||
|
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 = width.to_px();
|
||||||
|
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(self.origin.x.to_px() as AzFloat, self.origin.y.to_px() as AzFloat),
|
||||||
|
Size2D(self.size.width.to_px() as AzFloat, self.size.height.to_px() as AzFloat))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_azure_snapped_rect() -> Rect<AzFloat> {
|
||||||
|
Rect(Point2D(self.origin.x.to_px() as AzFloat + 0.5f as AzFloat,
|
||||||
|
self.origin.y.to_px() as AzFloat + 0.5f as AzFloat),
|
||||||
|
Size2D(self.size.width.to_px() as AzFloat,
|
||||||
|
self.size.height.to_px() as AzFloat))
|
||||||
|
}
|
||||||
|
}
|
137
src/servo-gfx-2/render_layers.rs
Normal file
137
src/servo-gfx-2/render_layers.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
use compositor::{LayerBuffer, LayerBufferSet};
|
||||||
|
use display_list::DisplayList;
|
||||||
|
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) };
|
||||||
|
}
|
||||||
|
|
157
src/servo-gfx-2/render_task.rs
Normal file
157
src/servo-gfx-2/render_task.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
use compositor::{Compositor, LayerBufferSet};
|
||||||
|
use font_context::FontContext;
|
||||||
|
use opts::Opts;
|
||||||
|
use render_context::RenderContext;
|
||||||
|
use render_layers::{RenderLayer, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/servo-gfx-2/resource/file_loader.rs
Normal file
29
src/servo-gfx-2/resource/file_loader.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
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(())));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
38
src/servo-gfx-2/resource/http_loader.rs
Normal file
38
src/servo-gfx-2/resource/http_loader.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
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(())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1101
src/servo-gfx-2/resource/image_cache_task.rs
Normal file
1101
src/servo-gfx-2/resource/image_cache_task.rs
Normal file
File diff suppressed because it is too large
Load diff
150
src/servo-gfx-2/resource/local_image_cache.rs
Normal file
150
src/servo-gfx-2/resource/local_image_cache.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
/*!
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
157
src/servo-gfx-2/resource/resource_task.rs
Normal file
157
src/servo-gfx-2/resource/resource_task.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
/*!
|
||||||
|
|
||||||
|
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
|
||||||
|
pub 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
|
||||||
|
pub 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);
|
||||||
|
}
|
93
src/servo-gfx-2/servo_gfx.rc
Normal file
93
src/servo-gfx-2/servo_gfx.rc
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#[link(name = "servo_gfx",
|
||||||
|
vers = "0.1",
|
||||||
|
uuid = "0106bb54-6ea9-45bf-a39e-a738621f15e5",
|
||||||
|
url = "http://servo.org/")];
|
||||||
|
#[crate_type = "lib"];
|
||||||
|
|
||||||
|
extern mod azure;
|
||||||
|
extern mod cairo;
|
||||||
|
extern mod geom;
|
||||||
|
extern mod http_client;
|
||||||
|
extern mod stb_image;
|
||||||
|
extern mod std;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Misc.
|
||||||
|
pub mod opts;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Images
|
||||||
|
pub mod image {
|
||||||
|
pub mod base;
|
||||||
|
pub mod encode {
|
||||||
|
pub mod tga;
|
||||||
|
}
|
||||||
|
pub mod holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Blech. This does not belong in the GFX module.
|
||||||
|
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 util {
|
||||||
|
pub mod cache;
|
||||||
|
pub mod range;
|
||||||
|
pub mod time;
|
||||||
|
pub mod url;
|
||||||
|
pub mod vec;
|
||||||
|
}
|
||||||
|
|
5
src/servo-gfx-2/servo_gfx.rs
Normal file
5
src/servo-gfx-2/servo_gfx.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// FIXME: Blech. We need crate-relative imports.
|
||||||
|
pub use servo_gfx_font = font;
|
||||||
|
pub use servo_gfx_font_list = font_list;
|
||||||
|
pub use servo_gfx_util = util;
|
||||||
|
|
40
src/servo-gfx-2/surface.rs
Normal file
40
src/servo-gfx-2/surface.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
9
src/servo-gfx-2/text.rs
Normal file
9
src/servo-gfx-2/text.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/* 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;
|
624
src/servo-gfx-2/text/glyph.rs
Normal file
624
src/servo-gfx-2/text/glyph.rs
Normal file
|
@ -0,0 +1,624 @@
|
||||||
|
use au = geometry;
|
||||||
|
use au::Au;
|
||||||
|
use servo_gfx_util::range::Range;
|
||||||
|
use servo_gfx_util::vec::*;
|
||||||
|
|
||||||
|
use core::cmp::{Ord, Eq};
|
||||||
|
use core::dvec::DVec;
|
||||||
|
use core::num::from_int;
|
||||||
|
use core::u16;
|
||||||
|
use geom::point::Point2D;
|
||||||
|
use std::sort;
|
||||||
|
|
||||||
|
|
||||||
|
// 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(&self, i: uint, cb: fn&(uint, GlyphInfo/&) -> bool) -> bool {
|
||||||
|
assert i < self.entry_buffer.len();
|
||||||
|
|
||||||
|
let entry = &self.entry_buffer[i];
|
||||||
|
match entry.is_simple() {
|
||||||
|
true => {
|
||||||
|
let proxy = SimpleGlyphInfo(self, i);
|
||||||
|
if !cb(i, proxy) { return false; }
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_glyphs_for_range(&self, range: Range, cb: fn&(uint, GlyphInfo/&) -> bool) {
|
||||||
|
assert range.begin() < self.entry_buffer.len();
|
||||||
|
assert range.end() <= self.entry_buffer.len();
|
||||||
|
|
||||||
|
for range.eachi |i| {
|
||||||
|
if !self.iter_glyphs_for_index(i, cb) { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_all_glyphs(cb: fn&(uint, GlyphInfo/&) -> bool) {
|
||||||
|
for uint::range(0, self.entry_buffer.len()) |i| {
|
||||||
|
if !self.iter_glyphs_for_index(i, cb) { break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
177
src/servo-gfx-2/text/harfbuzz/shaper.rs
Normal file
177
src/servo-gfx-2/text/harfbuzz/shaper.rs
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
extern mod harfbuzz;
|
||||||
|
|
||||||
|
use geometry::Au;
|
||||||
|
use glyph::{GlyphStore, GlyphIndex, GlyphData};
|
||||||
|
use servo_gfx_font::Font;
|
||||||
|
|
||||||
|
use core::libc::types::common::c99::int32_t;
|
||||||
|
use core::libc::{c_uint, c_int, c_void, c_char};
|
||||||
|
use core::ptr::{null, to_unsafe_ptr, offset};
|
||||||
|
use geom::Point2D;
|
||||||
|
use harfbuzz::{HB_MEMORY_MODE_READONLY, HB_DIRECTION_LTR, hb_blob_t, hb_face_t, hb_font_t};
|
||||||
|
use harfbuzz::{hb_font_funcs_t, hb_buffer_t, hb_codepoint_t, hb_bool_t, hb_glyph_position_t};
|
||||||
|
use harfbuzz::{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};
|
||||||
|
use harfbuzz::bindgen::{hb_font_create, hb_font_destroy, hb_buffer_create, hb_buffer_destroy};
|
||||||
|
use harfbuzz::bindgen::{hb_buffer_add_utf8, hb_shape, hb_buffer_get_glyph_infos};
|
||||||
|
use harfbuzz::bindgen::{hb_buffer_get_glyph_positions, hb_font_set_ppem, hb_font_set_scale};
|
||||||
|
use harfbuzz::bindgen::{hb_buffer_set_direction, hb_font_funcs_create, hb_font_funcs_destroy};
|
||||||
|
use harfbuzz::bindgen::{hb_font_set_funcs, hb_font_funcs_set_glyph_h_advance_func};
|
||||||
|
use harfbuzz::bindgen::{hb_font_funcs_set_glyph_func, hb_font_funcs_set_glyph_h_kerning_func};
|
||||||
|
use std::arc;
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
17
src/servo-gfx-2/text/shaper.rs
Normal file
17
src/servo-gfx-2/text/shaper.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
Shaper encapsulates a specific shaper, such as Harfbuzz,
|
||||||
|
Uniscribe, Pango, or Coretext.
|
||||||
|
|
||||||
|
Currently, only harfbuzz bindings are implemented.
|
||||||
|
*/
|
||||||
|
use servo_gfx_font::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)
|
||||||
|
}
|
||||||
|
}
|
203
src/servo-gfx-2/text/text_run.rs
Normal file
203
src/servo-gfx-2/text/text_run.rs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
use font_context::FontContext;
|
||||||
|
use geometry::Au;
|
||||||
|
use glyph::GlyphStore;
|
||||||
|
use servo_gfx_font::{Font, FontDescriptor, RunMetrics};
|
||||||
|
use servo_gfx_util::range::{Range, MutableRange};
|
||||||
|
|
||||||
|
use core::libc::{c_void};
|
||||||
|
use geom::point::Point2D;
|
||||||
|
use geom::size::Size2D;
|
||||||
|
use std::arc;
|
||||||
|
use std::arc::ARC;
|
||||||
|
|
||||||
|
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::new(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::new(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(~"", ~[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
223
src/servo-gfx-2/text/util.rs
Normal file
223
src/servo-gfx-2/text/util.rs
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
59
src/servo-gfx-2/util/cache.rs
Normal file
59
src/servo-gfx-2/util/cache.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
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();
|
||||||
|
}
|
164
src/servo-gfx-2/util/range.rs
Normal file
164
src/servo-gfx-2/util/range.rs
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
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::new(0, 0) }
|
||||||
|
|
||||||
|
pub struct MutableRange {
|
||||||
|
priv mut off: uint,
|
||||||
|
priv mut len: uint
|
||||||
|
}
|
||||||
|
|
||||||
|
pub impl MutableRange {
|
||||||
|
static pub pure fn new(off: uint, len: uint) -> MutableRange {
|
||||||
|
MutableRange { off: off, len: len }
|
||||||
|
}
|
||||||
|
|
||||||
|
static pub pure fn empty() -> MutableRange {
|
||||||
|
MutableRange::new(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
15
src/servo-gfx-2/util/time.rs
Normal file
15
src/servo-gfx-2/util/time.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
107
src/servo-gfx-2/util/url.rs
Normal file
107
src/servo-gfx-2/util/url.rs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
use core::path::Path;
|
||||||
|
use std::map::HashMap;
|
||||||
|
use std::net::url;
|
||||||
|
use std::net::url::Url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
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)]
|
||||||
|
pub 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";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type UrlMap<T: Copy> = HashMap<Url, T>;
|
||||||
|
|
||||||
|
pub fn url_map<T: Copy>() -> UrlMap<T> {
|
||||||
|
use core::to_str::ToStr;
|
||||||
|
|
||||||
|
HashMap::<Url, T>()
|
||||||
|
}
|
102
src/servo-gfx-2/util/vec.rs
Normal file
102
src/servo-gfx-2/util/vec.rs
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
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