Overhaul handling of glyphs, glyph shaping, text runs, and hacky line breaking.

This commit is contained in:
Brian J. Burg 2012-10-08 17:54:40 -07:00
parent a272a50635
commit 839ce9c867
15 changed files with 841 additions and 482 deletions

View file

@ -1,5 +1,5 @@
use azure::azure_hl::DrawTarget;
use gfx::render_task::{draw_solid_color, draw_image, draw_glyphs};
use gfx::render_task::{draw_solid_color, draw_image, draw_text};
use gfx::geometry::*;
use geom::rect::Rect;
use image::base::Image;
@ -7,30 +7,24 @@ use render_task::RenderContext;
use std::arc::{ARC, clone};
use dvec::DVec;
use text::glyph::Glyph;
use text::text_run::TextRun;
pub use layout::display_list_builder::DisplayListBuilder;
// TODO: invert this so common data is nested inside each variant as first arg.
struct DisplayItem {
draw: ~fn((&DisplayItem), (&RenderContext)),
bounds : Rect<au>, // TODO: whose coordinate system should this use?
data : DisplayItemData
}
enum DisplayItemData {
pub enum DisplayItemData {
SolidColorData(u8, u8, u8),
GlyphData(GlyphRun),
// 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.
TextData(~TextRun, uint, uint),
ImageData(ARC<~image::base::Image>),
PaddingData(u8, u8, u8, u8) // This is a hack to make fonts work (?)
}
/**
A run of glyphs in a single font. This is distinguished from any similar
structure used by layout in that this must be sendable, whereas the text
shaping data structures may end up unsendable.
*/
pub struct GlyphRun {
glyphs: ~[Glyph]
}
fn draw_SolidColor(self: &DisplayItem, ctx: &RenderContext) {
@ -40,9 +34,9 @@ fn draw_SolidColor(self: &DisplayItem, ctx: &RenderContext) {
}
}
fn draw_Glyphs(self: &DisplayItem, ctx: &RenderContext) {
fn draw_Text(self: &DisplayItem, ctx: &RenderContext) {
match self.data {
GlyphData(run) => draw_glyphs(ctx, self.bounds, &run),
TextData(~run, offset, len) => draw_text(ctx, self.bounds, &run, offset, len),
_ => fail
}
}
@ -56,25 +50,23 @@ fn draw_Image(self: &DisplayItem, ctx: &RenderContext) {
pub fn SolidColor(bounds: Rect<au>, r: u8, g: u8, b: u8) -> DisplayItem {
DisplayItem {
// TODO: this seems wrong.
draw: |self, ctx| draw_SolidColor(self, ctx),
bounds: bounds,
data: SolidColorData(r, g, b)
}
}
pub fn Glyphs(bounds: Rect<au>, run: GlyphRun) -> DisplayItem {
pub fn Text(bounds: Rect<au>, run: ~TextRun, offset: uint, length: uint) -> DisplayItem {
DisplayItem {
draw: |self, ctx| draw_Glyphs(self, ctx),
draw: |self, ctx| draw_Text(self, ctx),
bounds: bounds,
data: GlyphData(run)
data: TextData(run, offset, length)
}
}
// ARC should be cloned into ImageData, but Images are not sendable
pub fn Image(bounds: Rect<au>, image: ARC<~image::base::Image>) -> DisplayItem {
DisplayItem {
// TODO: this seems wrong.
draw: |self, ctx| draw_Image(self, ctx),
bounds: bounds,
data: ImageData(clone(&image))

View file

@ -39,20 +39,23 @@ pub fn box<A:Copy Num>(x: A, y: A, w: A, h: A) -> Rect<A> {
Rect(Point2D(x, y), Size2D(w, h))
}
pub fn zero_rect() -> Rect<au> {
pub pure fn zero_rect() -> Rect<au> {
let z = au(0);
Rect(Point2D(z, z), Size2D(z, z))
}
pub fn zero_point() -> Point2D<au> {
pub pure fn zero_point() -> Point2D<au> {
Point2D(au(0), au(0))
}
pub fn zero_size() -> Size2D<au> {
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)
}
@ -60,3 +63,8 @@ pub pure fn from_px(i: int) -> au {
pub pure fn to_px(au: au) -> int {
(*au / 60) as int
}
// assumes 72 points per inch, and 96 px per inch
pub pure fn from_pt(f: float) -> au {
from_int((f * 96f / 72f) as int)
}

View file

@ -1,28 +1,31 @@
use mod azure::azure_hl;
use au = geometry;
use au = gfx::geometry;
use au::au;
use platform::osmain;
use comm::*;
use image::base::Image;
use dl = display_list;
use libc::size_t;
use text::font::Font;
use display_list::GlyphRun;
use geom::size::Size2D;
use geom::rect::Rect;
use geom::point::Point2D;
use azure::{AzDrawOptions, AzFloat, AzGlyph, AzGlyphBuffer};
use azure::bindgen::AzDrawTargetFillGlyphs;
use azure::cairo::{cairo_font_face_t, cairo_scaled_font_t};
use azure::{AzDrawOptions, AzFloat, AzGlyph, AzGlyphBuffer};
use azure_hl::{AsAzureRect, B8G8R8A8, Color, ColorPattern, DrawOptions, DrawSurfaceOptions};
use azure_hl::{DrawTarget, Linear};
use comm::*;
use compositor::Compositor;
use core::dvec::DVec;
use dl = display_list;
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use image::base::Image;
use libc::size_t;
use pipes::{Port, Chan};
use platform::osmain;
use ptr::to_unsafe_ptr;
use std::arc::ARC;
use azure::cairo::{cairo_font_face_t, cairo_scaled_font_t};
use std::cell::Cell;
use compositor::Compositor;
use servo_text::font_cache::FontCache;
use text::text_run::TextRun;
use text::font::Font;
use text::font_cache::FontCache;
use pipes::{Port, Chan};
pub type Renderer = comm::Chan<Msg>;
@ -133,7 +136,7 @@ pub fn draw_image(ctx: &RenderContext, bounds: Rect<au>, image: ARC<~Image>) {
draw_options);
}
pub fn draw_glyphs(ctx: &RenderContext, bounds: Rect<au>, text_run: &GlyphRun) {
pub fn draw_text(ctx: &RenderContext, bounds: Rect<au>, run: &TextRun, offset: uint, length: uint) {
use ptr::{null};
use vec::raw::to_ptr;
use libc::types::common::c99::{uint16_t, uint32_t};
@ -147,7 +150,7 @@ pub fn draw_glyphs(ctx: &RenderContext, bounds: Rect<au>, text_run: &GlyphRun) {
AzReleaseColorPattern};
use azure::cairo::bindgen::cairo_scaled_font_destroy;
// FIXME: font should be accessible from GlyphRun
// FIXME: font should be accessible from TextRun
let font = ctx.font_cache.get_test_font();
let nfont: AzNativeFont = {
@ -175,22 +178,29 @@ pub fn draw_glyphs(ctx: &RenderContext, bounds: Rect<au>, text_run: &GlyphRun) {
};
let mut origin = Point2D(bounds.origin.x, bounds.origin.y.add(&bounds.size.height));
let azglyphs = text_run.glyphs.map(|glyph| {
let azglyphs = DVec();
azglyphs.reserve(length);
do run.glyphs.iter_glyphs_for_range(offset, length) |_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,
mIndex: glyph.index() as uint32_t,
mPosition: {
x: au::to_px(origin.x.add(&glyph.pos.offset.x)) as AzFloat,
y: au::to_px(origin.y.add(&glyph.pos.offset.y)) as AzFloat
x: au::to_px(origin.x + glyph_offset.x) as AzFloat,
y: au::to_px(origin.y + glyph_offset.y) as AzFloat
}
};
origin = Point2D(origin.x.add(&glyph.pos.advance.x),
origin.y.add(&glyph.pos.advance.y));
azglyph
});
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: to_ptr(azglyphs),
mNumGlyphs: azglyphs.len() as uint32_t
mGlyphs: to_ptr(azglyph_buf),
mNumGlyphs: azglyph_buf_len as uint32_t
}};
// TODO: this call needs to move into azure_hl.rs

View file

@ -66,7 +66,7 @@ impl FlowContext : BlockLayout {
/* TODO: floats */
/* TODO: absolute contexts */
/* TODO: inline-blocks */
fn bubble_widths_block(_ctx: &LayoutContext) {
fn bubble_widths_block(ctx: &LayoutContext) {
assert self.starts_block_flow();
let mut min_width = au(0);
@ -83,8 +83,8 @@ impl FlowContext : BlockLayout {
/* if not an anonymous block context, add in block box's widths.
these widths will not include child elements, just padding etc. */
do self.with_block_box |box| {
min_width = min_width.add(&box.get_min_width());
pref_width = pref_width.add(&box.get_pref_width());
min_width = min_width.add(&box.get_min_width(ctx));
pref_width = pref_width.add(&box.get_pref_width(ctx));
}
self.d().min_width = min_width;

View file

@ -93,7 +93,8 @@ enum RenderBoxType {
pub enum RenderBox {
GenericBox(RenderBoxData),
ImageBox(RenderBoxData, ImageHolder),
TextBox(RenderBoxData, TextBoxData)
TextBox(RenderBoxData, TextBoxData),
UnscannedTextBox(RenderBoxData, ~str)
}
impl RenderBox {
@ -101,7 +102,8 @@ impl RenderBox {
match *self {
GenericBox(ref d) => d,
ImageBox(ref d, _) => d,
TextBox(ref d, _) => d
TextBox(ref d, _) => d,
UnscannedTextBox(ref d, _) => d,
}
}
}
@ -130,7 +132,7 @@ impl RenderBox {
* may cause glyphs to be allocated. For now, it's impure because of
* holder.get_image()
*/
fn get_min_width() -> au {
fn get_min_width(ctx: &LayoutContext) -> au {
match self {
// TODO: this should account for min/pref widths of the
// box element in isolation. That includes
@ -141,13 +143,12 @@ impl RenderBox {
// TODO: consult CSS 'width', margin, border.
// TODO: If image isn't available, consult 'width'.
ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width),
TextBox(_,d) => d.runs.foldl(au(0), |sum, run| {
au::max(*sum, run.min_break_width())
})
TextBox(_,d) => d.run.min_width_for_range(ctx, d.offset, d.length),
UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here."
}
}
fn get_pref_width() -> au {
fn get_pref_width(_ctx: &LayoutContext) -> au {
match self {
// TODO: this should account for min/pref widths of the
// box element in isolation. That includes
@ -156,12 +157,30 @@ impl RenderBox {
// that of its children to arrive at the context width.
GenericBox(*) => au(0),
ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width),
// TODO: account for line breaks, etc. The run should know
// how to compute its own min and pref widths, and should
// probably cache them.
TextBox(_,d) => d.runs.foldl(au(0), |sum, run| {
au::max(*sum, run.size().width)
})
// a text box cannot span lines, so assume that this is an unsplit text box.
// TODO: If text boxes have been split to wrap lines, then
// they could report a smaller pref width during incremental reflow.
// maybe text boxes should report nothing, and the parent flow could
// factor in min/pref widths of any text runs that it owns.
TextBox(_,d) => {
let mut max_line_width: au = au(0);
do d.run.iter_natural_lines_for_range(d.offset, d.length) |line_offset, line_len| {
let mut line_width: au = au(0);
do d.run.glyphs.iter_glyphs_for_range(line_offset, line_len) |_char_i, glyph| {
line_width += glyph.advance()
};
if max_line_width < line_width {
max_line_width = line_width;
};
true
}
max_line_width
},
UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here."
}
}
@ -212,7 +231,8 @@ impl RenderBox {
},
TextBox(*) => {
copy self.d().position
}
},
UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here."
}
}
@ -251,25 +271,9 @@ impl RenderBox {
copy self.d().position.size);
match self {
UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here.",
TextBox(_,d) => {
let mut runs = d.runs;
// TODO: don't paint background for text boxes
list.push(~dl::SolidColor(bounds, 255u8, 255u8, 255u8));
let mut bounds = bounds;
for uint::range(0, runs.len()) |i| {
bounds.size.height = runs[i].size().height;
let glyph_run = make_glyph_run(&runs[i]);
list.push(~dl::Glyphs(bounds, glyph_run));
bounds.origin.y = bounds.origin.y.add(&bounds.size.height);
}
return;
pure fn make_glyph_run(text_run: &TextRun) -> dl::GlyphRun {
dl::GlyphRun {
glyphs: copy text_run.glyphs
}
}
list.push(~dl::Text(bounds, ~(copy *d.run), d.offset, d.length))
},
// TODO: items for background, border, outline
GenericBox(*) => { },
@ -348,12 +352,8 @@ impl RenderBox : BoxedDebugMethods {
let repr = match self {
@GenericBox(*) => ~"GenericBox",
@ImageBox(*) => ~"ImageBox",
@TextBox(_,d) => {
let mut s = d.runs.foldl(~"TextBox(runs=", |s, run| {
fmt!("%s \"%s\"", *s, run.text)
});
s += ~")"; s
}
@TextBox(_,d) => fmt!("TextBox(text=%s)", str::substr(d.run.text, d.offset, d.length)),
@UnscannedTextBox(_,s) => fmt!("UnscannedTextBox(%s)", s)
};
fmt!("box b%?: %?", self.d().id, repr)

View file

@ -13,10 +13,7 @@ use layout::context::LayoutContext;
use layout::flow::*;
use layout::inline::InlineFlowData;
use layout::root::RootFlowData;
use layout::text::TextBoxData;
use option::is_none;
use servo_text::font_cache::FontCache;
use servo_text::text_run::TextRun;
use util::tree;
export LayoutTreeBuilder;
@ -242,16 +239,10 @@ impl LayoutTreeBuilder {
}
fn make_text_box(layout_ctx: &LayoutContext, node: Node, ctx: @FlowContext) -> @RenderBox {
fn make_text_box(_layout_ctx: &LayoutContext, node: Node, ctx: @FlowContext) -> @RenderBox {
do node.read |n| {
match n.kind {
~Text(string) => {
// TODO: clean this up. Fonts should not be created here.
let font = layout_ctx.font_cache.get_test_font();
let run = TextRun(font, string);
@TextBox(RenderBoxData(node, ctx, self.next_box_id()),
TextBoxData(copy string, ~[move run]))
},
~Text(string) => @UnscannedTextBox(RenderBoxData(node, ctx, self.next_box_id()), copy string),
_ => fail ~"WAT error: why couldn't we make a text box?"
}
}

View file

@ -8,10 +8,13 @@ use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use gfx::geometry::au;
use layout::box::{RenderBox, RenderBoxTree, ImageBox, TextBox, GenericBox};
use layout::flow::{FlowContext, InlineFlow};
use layout::box::{RenderBox, RenderBoxTree, ImageBox, TextBox, GenericBox, UnscannedTextBox};
use layout::context::LayoutContext;
use layout::flow::{FlowContext, InlineFlow};
use layout::text::TextBoxData;
use num::Num;
use servo_text::text_run::TextRun;
use std::arc;
use util::tree;
/*
@ -40,10 +43,24 @@ hard to try out that alternative.
type BoxRange = {start: u8, len: u8};
// TODO: flesh out into TextRunScanner
fn build_runs_for_flow(ctx: &LayoutContext, dummy_boxes: &DVec<@RenderBox>) {
for uint::range(0, dummy_boxes.len()) |i| {
match *dummy_boxes[i] {
UnscannedTextBox(d, text) => {
let run = TextRun(&*ctx.font_cache.get_test_font(), text);
let box_guts = TextBoxData(@run, 0, text.len());
dummy_boxes.set_elt(i, @TextBox(d, box_guts));
},
_ => {}
}
}
}
struct InlineFlowData {
// A flat list of all inline render boxes. Several boxes may
// A vec of all inline render boxes. Several boxes may
// correspond to one Node/Element.
boxes: DList<@RenderBox>,
boxes: DVec<@RenderBox>,
// vec of ranges into boxes that represents line positions.
// these ranges are disjoint, and are the result of inline layout.
lines: DVec<BoxRange>,
@ -55,7 +72,7 @@ struct InlineFlowData {
fn InlineFlowData() -> InlineFlowData {
InlineFlowData {
boxes: DList(),
boxes: DVec(),
lines: DVec(),
elems: DVec()
}
@ -73,15 +90,18 @@ trait InlineLayout {
impl FlowContext : InlineLayout {
pure fn starts_inline_flow() -> bool { match self { InlineFlow(*) => true, _ => false } }
fn bubble_widths_inline(_ctx: &LayoutContext) {
fn bubble_widths_inline(ctx: &LayoutContext) {
assert self.starts_inline_flow();
// TODO: this is a hack
build_runs_for_flow(ctx, &self.inline().boxes);
let mut min_width = au(0);
let mut pref_width = au(0);
for self.inline().boxes.each |box| {
min_width = au::max(min_width, box.get_min_width());
pref_width = au::max(pref_width, box.get_pref_width());
min_width = au::max(min_width, box.get_min_width(ctx));
pref_width = au::max(pref_width, box.get_pref_width(ctx));
}
self.d().min_width = min_width;
@ -101,6 +121,9 @@ impl FlowContext : InlineLayout {
//let mut cur_x = au(0);
let mut cur_y = au(0);
// TODO: remove test font uses
let test_font = ctx.font_cache.get_test_font();
for self.inline().boxes.each |box| {
/* TODO: actually do inline flow.
- Create a working linebox, and successively put boxes
@ -111,25 +134,26 @@ impl FlowContext : InlineLayout {
- Save the dvec of this context's lineboxes. */
/* hack: until text box splitting is hoisted into this
function, force "reflow" on TextBoxes. */
match *box {
@TextBox(*) => box.reflow_text(ctx),
_ => {}
}
box.d().position.size.width = match *box {
@ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).width),
@TextBox(_,d) => d.runs[0].size().width,
@TextBox(_,d) => {
// TODO: measure twice, cut once doesn't apply to text. Shouldn't need
// to measure text again here (should be inside TextBox.split)
let metrics = test_font.measure_text(d.run, d.offset, d.length);
metrics.advance
},
// TODO: this should be set to the extents of its children
@GenericBox(*) => au(0)
@GenericBox(*) => au(0),
_ => fail fmt!("Tried to assign width to unknown Box variant: %?", box)
};
box.d().position.size.height = match *box {
@ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).height),
@TextBox(_,d) => d.runs[0].size().height,
// TODO: we should use the bounding box of the actual text, i think?
@TextBox(*) => test_font.metrics.em_size,
// TODO: this should be set to the extents of its children
@GenericBox(*) => au(0)
@GenericBox(*) => au(0),
_ => fail fmt!("Tried to assign width to unknown Box variant: %?", box)
};
box.d().position.origin = Point2D(au(0), cur_y);
@ -161,7 +185,7 @@ impl FlowContext : InlineLayout {
// TODO: once we form line boxes and have their cached bounds, we can be
// smarter and not recurse on a line if nothing in it can intersect dirty
debug!("building display list for %u", self.inline().boxes.len());
debug!("building display list for %u inline boxes", self.inline().boxes.len());
for self.inline().boxes.each |box| {
box.build_display_list(builder, dirty, offset, list)
}

View file

@ -1,30 +1,28 @@
/** Text layout. */
use au = gfx::geometry;
use au::au;
use geom::size::Size2D;
use gfx::geometry::au;
use servo_text::text_run::TextRun;
use servo_text::font_cache::FontCache;
use layout::box::{TextBox, RenderBox};
use layout::context::LayoutContext;
struct TextBoxData {
text: ~str,
mut runs: ~[TextRun]
pub struct TextBoxData {
run: @TextRun,
offset: uint,
length: uint
}
fn TextBoxData(text: ~str, runs: ~[TextRun]) -> TextBoxData {
pub fn TextBoxData(run: @TextRun, offset: uint, length: uint) -> TextBoxData {
TextBoxData {
text: text,
runs: runs
run: run,
offset: offset,
length: length
}
}
trait TextLayout {
fn reflow_text(ctx: &LayoutContext);
}
/** The main reflow routine for text layout. */
/* The main reflow routine for text layout.
impl @RenderBox : TextLayout {
fn reflow_text(ctx: &LayoutContext) {
let d = match self {
@ -32,6 +30,7 @@ impl @RenderBox : TextLayout {
_ => { fail ~"expected text box in reflow_text!" }
};
// TODO: get font from textrun's TextStyle
let font = ctx.font_cache.get_test_font();
// Do line breaking.
@ -76,26 +75,4 @@ impl @RenderBox : TextLayout {
self.d().position.size = Size2D(max_width, total_height);
d.runs = move dvec::unwrap(lines);
}
}
/* TODO: new unit tests for TextBox splitting, etc
fn should_calculate_the_size_of_the_text_box() {
#[test];
#[ignore(cfg(target_os = "macos"))];
use au = gfx::geometry;
use dom::rcu::{Scope};
use dom::base::{Text, NodeScope};
use util::tree;
use layout::box_builder::LayoutTreeBuilder;
let s = Scope();
let n = s.new_node(Text(~"firecracker"));
let builder = LayoutTreeBuilder();
let b = builder.construct_trees(n).get();
b.reflow_text();
let expected = Size2D(au::from_px(84), au::from_px(20));
assert b.data.position.size == expected;
}
*/
}*/

View file

@ -1,11 +1,16 @@
pub use font_cache::FontCache;
export Font, FontMetrics, test_font_bin, create_test_font;
use au = gfx::geometry;
use au::au;
use glyph::GlyphIndex;
use vec_to_ptr = vec::raw::to_ptr;
use libc::{ c_int, c_double, c_ulong };
use ptr::{ null, addr_of };
use native_font::NativeFont;
use ptr::{null, addr_of};
use text::text_run::TextRun;
use vec_to_ptr = vec::raw::to_ptr;
// Used to abstract over the shaper's choice of fixed int representation.
type FractionalPixel = float;
/**
A font handle. Layout can use this to calculate glyph metrics
@ -16,11 +21,38 @@ struct Font {
lib: @FontCache,
fontbuf: @~[u8],
native_font: NativeFont,
metrics: FontMetrics
metrics: FontMetrics,
}
impl Font {
fn buf() -> @~[u8] {
struct RunMetrics {
advance: au,
}
// Public API
pub trait FontMethods {
fn measure_text(run: &TextRun, offset: uint, length: uint) -> RunMetrics;
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 measure_text(run: &TextRun, offset: uint, length: uint) -> RunMetrics {
// TODO: alter advance direction for RTL
// TODO: calculate actual bounding box as part of RunMetrics
// TODO: using inter-char and inter-word spacing settings when measuring text
let mut advance = au(0);
do run.glyphs.iter_glyphs_for_range(offset, length) |_i, glyph| {
advance += glyph.advance()
}
RunMetrics { advance: advance }
}
fn buf(&self) -> @~[u8] {
self.fontbuf
}
@ -28,14 +60,16 @@ impl Font {
self.native_font.glyph_index(codepoint)
}
fn glyph_h_advance(glyph: GlyphIndex) -> int {
fn glyph_h_advance(glyph: GlyphIndex) -> FractionalPixel {
match self.native_font.glyph_h_advance(glyph) {
Some(adv) => adv,
None => /* FIXME: Need fallback strategy */ 10
None => /* FIXME: Need fallback strategy */ 10f as FractionalPixel
}
}
}
// TODO: font should compute its own metrics using native_font.
// TODO: who should own fontbuf?
fn Font(lib: @FontCache, fontbuf: @~[u8], +native_font: NativeFont, +metrics: FontMetrics) -> Font {
Font {
lib: lib,
@ -45,12 +79,19 @@ fn Font(lib: @FontCache, fontbuf: @~[u8], +native_font: NativeFont, +metrics: Fo
}
}
// TODO: what are the units of these metrics? CTFont metrics calls are
// font units for a specific point size, and these are normalized by
// font units-per-em. Are there some cases where these metrics depend
// on font size? If so, need to be careful about using same sizes on
// font creation, metrics gathering, and drawing.
struct FontMetrics {
underline_size: float,
underline_offset: float,
leading: float,
x_height: float,
// how many appunits an em is equivalent to (based on point-to-au)
em_size: au,
em_height: float,
em_ascent: float,
em_descent: float,

View file

@ -2,22 +2,57 @@ use au = gfx::geometry;
use au::au;
use core::cmp::{Ord, Eq};
use core::dvec::DVec;
use core::u16;
use geom::point::Point2D;
use std::sort;
use servo_util::vec::*;
use num::from_int;
export GlyphIndex, GlyphPos, Glyph;
struct CompressedGlyph {
mut value : u32
// 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_HYPEN : u8 = 0x2u8;
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.
@ -35,7 +70,7 @@ 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 CompressedGlyph to
// 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
@ -65,24 +100,28 @@ pure fn is_simple_advance(advance: au) -> bool {
type DetailedGlyphCount = u16;
enum GlyphStoreResult<T> {
Simple(T),
Detailed(u32)
pure fn InitialGlyphEntry() -> GlyphEntry {
GlyphEntry { value: 0 }
}
fn SimpleGlyph(index: GlyphIndex, advance: au) -> CompressedGlyph {
// 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;
CompressedGlyph {
GlyphEntry {
value: index_mask | advance_mask | FLAG_IS_SIMPLE_GLYPH
}
}
fn ComplexGlyph(startsCluster: bool, startsLigature: bool, glyphCount: u16) -> CompressedGlyph {
// 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 {
@ -93,42 +132,47 @@ fn ComplexGlyph(startsCluster: bool, startsLigature: bool, glyphCount: u16) -> C
}
val |= (glyphCount as u32) << GLYPH_COUNT_SHIFT;
CompressedGlyph {
GlyphEntry {
value: val
}
}
fn MissingGlyphs(glyphCount: u16) -> CompressedGlyph {
CompressedGlyph {
// 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
}
}
impl CompressedGlyph {
pure fn advance() -> GlyphStoreResult<au> {
match self.is_simple() {
true => Simple(num::from_int(((self.value & GLYPH_ADVANCE_MASK) >> GLYPH_ADVANCE_SHIFT) as int)),
false => Detailed(self.glyph_count())
}
// 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 glyph() -> GlyphStoreResult<GlyphIndex> {
match self.is_simple() {
true => Simple(self.value & GLYPH_ID_MASK),
false => Detailed(self.glyph_count())
}
pure fn index() -> GlyphIndex {
assert self.is_simple();
self.value & GLYPH_ID_MASK
}
pure fn offset() -> GlyphStoreResult<Point2D<au>> {
match self.is_simple() {
true => Simple(Point2D(au(0), au(0))),
false => Detailed(self.glyph_count())
}
pure fn offset() -> Point2D<au> {
assert self.is_simple();
Point2D(au(0), au(0))
}
// getter methods
pure fn is_ligature_start() -> bool {
self.has_flag(!FLAG_NOT_LIGATURE_GROUP_START)
}
// TODO: some getters are still missing; add them as needed.
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.
@ -144,46 +188,47 @@ impl CompressedGlyph {
!self.is_simple() && self.has_flag(FLAG_CHAR_IS_NEWLINE)
}
// TODO: make typesafe break enum
pure fn can_break_before() -> u8 {
((self.value & FLAG_CAN_BREAK_MASK) >> FLAG_CAN_BREAK_SHIFT) as u8
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
fn set_is_space() {
self.value |= FLAG_CHAR_IS_SPACE;
pure fn set_char_is_space() -> GlyphEntry {
GlyphEntry(self.value | FLAG_CHAR_IS_SPACE)
}
fn set_is_tab() {
pure fn set_char_is_tab() -> GlyphEntry {
assert !self.is_simple();
self.value |= FLAG_CHAR_IS_TAB;
GlyphEntry(self.value | FLAG_CHAR_IS_TAB)
}
fn set_is_newline() {
pure fn set_char_is_newline() -> GlyphEntry {
assert !self.is_simple();
self.value |= FLAG_CHAR_IS_NEWLINE;
GlyphEntry(self.value | FLAG_CHAR_IS_NEWLINE)
}
// returns whether the setting had changed.
fn set_can_break_before(flags: u8) -> bool {
assert flags <= 0x2;
let mask = (flags as u32) << FLAG_CAN_BREAK_SHIFT;
// 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);
self.value ^= toggle;
toggle as bool
match (toggle as bool) {
true => Some(GlyphEntry(self.value ^ toggle)),
false => None
}
}
// helper methods
/*priv*/ pure fn glyph_count() -> u32 {
/*priv*/ pure fn glyph_count() -> u16 {
assert !self.is_simple();
(self.value & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT as u32
((self.value & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT) as u16
}
/*priv*/ pure fn is_simple() -> bool {
(self.value & FLAG_IS_SIMPLE_GLYPH) == self.value
pure fn is_simple() -> bool {
self.has_flag(FLAG_IS_SIMPLE_GLYPH)
}
/*priv*/ pure fn has_flag(flag: u32) -> bool {
@ -191,93 +236,127 @@ impl CompressedGlyph {
}
}
// 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 {
// The GlyphIndex, or the unicode codepoint if glyph is missing.
index: u32,
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>
}
// cg = CompressedGlyph,
// dg = DetailedGlyph
fn DetailedGlyph(index: GlyphIndex,
advance: au, offset: Point2D<au>) -> DetailedGlyph {
DetailedGlyph {
index: index,
advance: advance,
offset: offset
}
}
struct DetailedGlyphRecord {
// source character/CompressedGlyph offset in the TextRun
cg_offset: u32,
// source string offset/GlyphEntry offset in the TextRun
entry_offset: uint,
// offset into the detailed glyphs buffer
dg_offset: uint
detail_offset: uint
}
impl DetailedGlyphRecord : Ord {
pure fn lt(other: &DetailedGlyphRecord) -> bool { self.cg_offset < other.cg_offset }
pure fn le(other: &DetailedGlyphRecord) -> bool { self.cg_offset <= other.cg_offset }
pure fn ge(other: &DetailedGlyphRecord) -> bool { self.cg_offset >= other.cg_offset }
pure fn gt(other: &DetailedGlyphRecord) -> bool { self.cg_offset > other.cg_offset }
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.cg_offset == other.cg_offset }
pure fn ne(other : &DetailedGlyphRecord) -> bool { self.cg_offset != other.cg_offset }
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.
// 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 {
dg_buffer: DVec<DetailedGlyph>,
dg_lookup: DVec<DetailedGlyphRecord>,
detail_buffer: DVec<DetailedGlyph>,
detail_lookup: DVec<DetailedGlyphRecord>,
mut lookup_is_sorted: bool,
}
fn DetailedGlyphStore() -> DetailedGlyphStore {
DetailedGlyphStore {
dg_buffer: DVec(),
dg_lookup: DVec(),
detail_buffer: DVec(),
detail_lookup: DVec(),
lookup_is_sorted: false
}
}
impl DetailedGlyphStore {
fn add_glyphs_for_cg(cg_offset: u32, glyphs: &[DetailedGlyph]) {
fn add_detailed_glyphs_for_entry(entry_offset: uint, glyphs: &[DetailedGlyph]) {
let entry = DetailedGlyphRecord {
cg_offset: cg_offset,
dg_offset: self.dg_buffer.len()
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.
/* 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.dg_lookup.borrow |arr| {
do self.detail_lookup.borrow |arr| {
assert !arr.contains(entry)
}
*/
self.dg_lookup.push(entry);
self.dg_buffer.push_all(glyphs);
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_glyphs_for_cg(&self, cg_offset: u32, count: uint) -> &[DetailedGlyph] {
assert count > 0 && count < self.dg_buffer.len();
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 {
cg_offset: cg_offset,
dg_offset: 0 // unused
entry_offset: entry_offset,
detail_offset: 0 // unused
};
do self.dg_lookup.borrow |records : &[DetailedGlyphRecord]| {
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.dg_buffer.borrow |glyphs : &[DetailedGlyph]| {
assert i + count < glyphs.len();
do self.detail_buffer.borrow |glyphs : &[DetailedGlyph]| {
assert i + (count as uint) < glyphs.len();
// return a view into the buffer
vec::view(glyphs, i, count)
vec::view(glyphs, 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)]
}
}
}
@ -289,54 +368,252 @@ impl DetailedGlyphStore {
return;
}
do self.dg_lookup.borrow_mut |arr| {
do self.detail_lookup.borrow_mut |arr| {
sort::quick_sort3(arr);
};
self.lookup_is_sorted = true;
}
}
// Public data structure and API for storing glyph data
// 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 {
mut cg_buffer: ~[CompressedGlyph],
dg_store: DetailedGlyphStore,
// 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.
// Use the get_glyph_data method to retrieve glyph data for a char..
fn GlyphStore(_text: ~str) {
fn GlyphStore(text: &str) -> GlyphStore {
assert text.len() > 0;
let buffer = vec::from_elem(text.len(), InitialGlyphEntry());
GlyphStore {
entry_buffer: dvec::from_vec(buffer),
detail_store: DetailedGlyphStore(),
}
}
impl GlyphStore {
}
fn add_glyph_for_index(i: uint, data: &GlyphData) {
// XXX: legacy glyphs below. rip out once converted to new glyphs
pure fn glyph_is_compressible(data: &GlyphData) -> bool {
is_simple_glyph_id(data.index)
&& is_simple_advance(data.advance)
&& data.offset == au::zero_point()
}
/** The position of a glyph on the screen. */
struct GlyphPos {
advance: Point2D<au>,
offset: Point2D<au>,
}
assert i < self.entry_buffer.len();
fn GlyphPos(advance: Point2D<au>, offset: Point2D<au>) -> GlyphPos {
GlyphPos {
advance : advance,
offset : offset,
}
}
/** A single glyph. */
struct Glyph {
index: u32,
pos: GlyphPos,
}
fn Glyph(index: u32, pos: GlyphPos) -> Glyph {
Glyph {
index : index,
pos : copy pos,
let entry = match (data.is_missing, glyph_is_compressible(data)) {
(true, _) => MissingGlyphsEntry(1),
(false, true) => { SimpleGlyphEntry(data.index, data.advance) },
(false, false) => {
let glyph = [DetailedGlyph(data.index, data.advance, data.offset)];
self.detail_store.add_detailed_glyphs_for_entry(i, glyph);
ComplexGlyphEntry(data.cluster_start, data.ligature_start, 1)
}
};
self.entry_buffer.set_elt(i, entry);
}
fn add_glyphs_for_index(i: uint, data_for_glyphs: &[GlyphData]) {
assert i < self.entry_buffer.len();
assert data_for_glyphs.len() > 0;
let glyph_count = data_for_glyphs.len();
let first_glyph_data = data_for_glyphs[0];
let entry = match first_glyph_data.is_missing {
true => MissingGlyphsEntry(glyph_count),
false => {
let glyphs_vec = vec::from_fn(glyph_count, |i| {
DetailedGlyph(data_for_glyphs[i].index,
data_for_glyphs[i].advance,
data_for_glyphs[i].offset)
});
self.detail_store.add_detailed_glyphs_for_entry(i, glyphs_vec);
ComplexGlyphEntry(first_glyph_data.cluster_start,
first_glyph_data.ligature_start,
glyph_count)
}
};
self.entry_buffer.set_elt(i, entry);
}
fn iter_glyphs_for_index<T>(&self, i: uint, cb: fn&(uint, GlyphInfo/&) -> T) {
assert i < self.entry_buffer.len();
let entry = &self.entry_buffer[i];
match entry.is_simple() {
true => {
let proxy = SimpleGlyphInfo(self, i);
cb(i, proxy);
},
false => {
let glyphs = self.detail_store.get_detailed_glyphs_for_entry(i, entry.glyph_count());
for uint::range(0, glyphs.len()) |j| {
let proxy = DetailGlyphInfo(self, i, j as u16);
cb(i, proxy);
}
}
}
}
fn iter_glyphs_for_range<T>(&self, offset: uint, len: uint, cb: fn&(uint, GlyphInfo/&) -> T) {
assert offset < self.entry_buffer.len();
assert len > 0 && len + offset <= self.entry_buffer.len();
for uint::range(offset, offset + len) |i| {
self.iter_glyphs_for_index(i, cb);
}
}
fn iter_all_glyphs<T>(cb: fn&(uint, GlyphInfo/&) -> T) {
for uint::range(0, self.entry_buffer.len()) |i| {
self.iter_glyphs_for_index(i, cb);
}
}
// getter methods
fn char_is_space(i: uint) -> bool {
assert i < self.entry_buffer.len();
self.entry_buffer[i].char_is_space()
}
fn char_is_tab(i: uint) -> bool {
assert i < self.entry_buffer.len();
self.entry_buffer[i].char_is_tab()
}
fn char_is_newline(i: uint) -> bool {
assert i < self.entry_buffer.len();
self.entry_buffer[i].char_is_newline()
}
fn is_ligature_start(i: uint) -> bool {
assert i < self.entry_buffer.len();
self.entry_buffer[i].is_ligature_start()
}
fn is_cluster_start(i: uint) -> bool {
assert i < self.entry_buffer.len();
self.entry_buffer[i].is_cluster_start()
}
fn can_break_before(i: uint) -> BreakType {
assert i < self.entry_buffer.len();
self.entry_buffer[i].can_break_before()
}
// setter methods
fn set_char_is_space(i: uint) {
assert i < self.entry_buffer.len();
let entry = self.entry_buffer[i];
self.entry_buffer.set_elt(i, entry.set_char_is_space())
}
fn set_char_is_tab(i: uint) {
assert i < self.entry_buffer.len();
let entry = self.entry_buffer[i];
self.entry_buffer.set_elt(i, entry.set_char_is_tab())
}
fn set_char_is_newline(i: uint) {
assert i < self.entry_buffer.len();
let entry = self.entry_buffer[i];
self.entry_buffer.set_elt(i, entry.set_char_is_newline())
}
fn set_can_break_before(i: uint, t: BreakType) {
assert i < self.entry_buffer.len();
let entry = self.entry_buffer[i];
match entry.set_can_break_before(t) {
Some(e) => self.entry_buffer.set_elt(i, e),
None => {}
};
}
}

View file

@ -1,6 +1,7 @@
#[legacy_exports];
export FreeTypeNativeFont, with_test_native_font, create;
use util::*;
use vec_as_buf = vec::as_imm_buf;
use ptr::{addr_of, null};
use cast::reinterpret_cast;
@ -18,6 +19,14 @@ use freetype::bindgen::{
FT_Set_Char_Size
};
fn float_to_fixed_ft(f: float) -> i32 {
float_to_fixed(26, f)
}
fn fixed_to_float_ft(f: i32) -> float {
fixed_to_float(26, f)
}
struct FreeTypeNativeFont {
/// The font binary. This must stay valid for the lifetime of the font
buf: @~[u8],
@ -44,13 +53,13 @@ impl FreeTypeNativeFont {
return if idx != 0 as FT_UInt {
Some(idx as GlyphIndex)
} else {
#warn("Invalid codepoint: %?", codepoint);
debug!("Invalid codepoint: %?", codepoint);
None
};
}
// FIXME: What unit is this returning? Let's have a custom type
fn glyph_h_advance(glyph: GlyphIndex) -> Option<int> {
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() {
@ -59,13 +68,11 @@ impl FreeTypeNativeFont {
let slot: FT_GlyphSlot = reinterpret_cast(&void_glyph);
assert slot.is_not_null();
let advance = (*slot).metrics.horiAdvance;
#debug("h_advance for %? is %?", glyph, advance);
// FIXME: Dividing by 64 converts to pixels, which
// is not the unit we should be using
return Some((advance / 64) as int);
debug!("h_advance for %? is %?", glyph, advance);
return Some(fixed_to_float_ft(advance) as FractionalPixel);
}
} else {
#warn("Unable to load glyph %?. reason: %?", glyph, res);
debug!("Unable to load glyph %?. reason: %?", glyph, res);
return None;
}
}

View file

@ -2,8 +2,9 @@ extern mod cocoa;
export QuartzNativeFont, with_test_native_font, create;
use font::FontMetrics;
use font::{FontMetrics, FractionalPixel};
use au = gfx::geometry;
use libc::size_t;
use ptr::null;
use glyph::GlyphIndex;
@ -130,8 +131,7 @@ impl QuartzNativeFont {
return Some(glyphs[0] as GlyphIndex);
}
// FIXME: What unit is this returning? Let's have a custom type
fn glyph_h_advance(glyph: GlyphIndex) -> Option<int> {
fn glyph_h_advance(glyph: GlyphIndex) -> Option<FractionalPixel> {
use coretext::{CGGlyph, kCTFontDefaultOrientation};
use coretext::coretext::{CTFontGetAdvancesForGlyphs};
@ -141,7 +141,7 @@ impl QuartzNativeFont {
CTFontGetAdvancesForGlyphs(self.ctfont, kCTFontDefaultOrientation, glyph_buf, null(), 1)
};
return Some(advance as int);
return Some(advance as FractionalPixel);
}
fn get_metrics() -> FontMetrics {
@ -164,6 +164,7 @@ impl QuartzNativeFont {
em_ascent: CTFontGetAscent(ctfont) as float * convFactor,
em_descent: CTFontGetDescent(ctfont) as float * convFactor,
em_height: em_ascent + em_descent,
em_size: au::from_pt(21f),
max_advance: bounding_rect.size.width as float * convFactor,
}
}
@ -174,6 +175,7 @@ fn ctfont_from_cgfont(cgfont: CGFontRef) -> coretext::CTFontRef {
use coretext::coretext::CTFontCreateWithGraphicsFont;
assert cgfont.is_not_null();
// TODO: use actual font size here!
CTFontCreateWithGraphicsFont(cgfont, 21f as CGFloat, null(), null())
}

View file

@ -1,23 +1,26 @@
extern mod harfbuzz;
export shape_text;
use au = gfx::geometry;
use au::au;
use core::num::from_int;
use font::Font;
use font_cache::FontCache;
use geom::point::Point2D;
use glyph::{GlyphStore, GlyphIndex, GlyphData};
use libc::types::common::c99::int32_t;
use libc::{c_uint, c_int, c_void, c_char};
use font::Font;
use glyph::{Glyph, GlyphPos, GlyphIndex};
use ptr::{null, to_unsafe_ptr, offset};
use gfx::geometry::au;
use geom::point::Point2D;
use font_cache::FontCache;
use std::arc;
use text_run::TextRun;
use util::*;
use cast::reinterpret_cast;
use harfbuzz::{HB_MEMORY_MODE_READONLY,
HB_DIRECTION_LTR};
use harfbuzz::{hb_blob_t, hb_face_t, hb_font_t, hb_buffer_t,
use harfbuzz::{hb_blob_t, hb_face_t, hb_font_t, hb_font_funcs_t, hb_buffer_t,
hb_codepoint_t, hb_bool_t, hb_glyph_position_t,
hb_var_int_t, hb_position_t};
hb_glyph_info_t, hb_var_int_t, hb_position_t};
use harfbuzz::bindgen::{hb_blob_create, hb_blob_destroy,
hb_face_create, hb_face_destroy,
hb_font_create, hb_font_destroy,
@ -33,75 +36,101 @@ use harfbuzz::bindgen::{hb_blob_create, hb_blob_destroy,
hb_font_funcs_set_glyph_func,
hb_font_funcs_set_glyph_h_kerning_func};
#[doc = "
fn float_to_fixed_hb(f: float) -> i32 {
util::float_to_fixed(16, f)
}
fn fixed_to_float_hb(i: hb_position_t) -> float {
util::fixed_to_float(16, i)
}
fn fixed_to_rounded_int_hb(f: hb_position_t) -> int {
util::fixed_to_rounded_int(16, f)
}
/**
Calculate the layout metrics associated with a some given text
when rendered in a specific font.
"]
fn shape_text(font: &Font, text: &str) -> ~[Glyph] unsafe {
#debug("shaping text '%s'", text);
*/
pub fn shape_textrun(font: &Font, run: &TextRun) {
debug!("shaping text '%s'", run.text);
let face_blob = vec::as_imm_buf(*(*font).buf(), |buf, len| {
hb_blob_create(reinterpret_cast(&buf),
// TODO: harfbuzz fonts and faces should be cached on the Font object.
// TODO: font tables should be stored in Font object and cached by FontCache (Issue #92)
let face_blob: *hb_blob_t = vec::as_imm_buf(*(*font).fontbuf, |buf: *u8, len: uint| {
hb_blob_create(buf as *c_char,
len as c_uint,
HB_MEMORY_MODE_READONLY,
null(),
null())
});
let hbface = hb_face_create(face_blob, 0 as c_uint);
let hbfont = hb_font_create(hbface);
let hb_face: *hb_face_t = hb_face_create(face_blob, 0 as c_uint);
let hb_font: *hb_font_t = hb_font_create(hb_face);
hb_font_set_ppem(hbfont, 10 as c_uint, 10 as c_uint);
hb_font_set_scale(hbfont, 10 as c_int, 10 as c_int);
// TODO: set font size here, based on Font's size
// Set points-per-em. if zero, performs no hinting in that direction.
hb_font_set_ppem(hb_font, 21 as c_uint, 21 as c_uint);
// Set scaling. Note that this takes 16.16 fixed point.
hb_font_set_scale(hb_font, float_to_fixed_hb(21f) as c_int, float_to_fixed_hb(21f) as c_int);
let funcs = hb_font_funcs_create();
let funcs: *hb_font_funcs_t = hb_font_funcs_create();
hb_font_funcs_set_glyph_func(funcs, glyph_func, null(), null());
hb_font_funcs_set_glyph_h_advance_func(funcs, glyph_h_advance_func, null(), null());
hb_font_set_funcs(hbfont, funcs, reinterpret_cast(&to_unsafe_ptr(font)), null());
let buffer = hb_buffer_create();
unsafe {
let font_data: *c_void = cast::transmute(font);
hb_font_set_funcs(hb_font, funcs, font_data, null());
};
hb_buffer_set_direction(buffer, HB_DIRECTION_LTR);
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, _l| {
hb_buffer_add_utf8(buffer, ctext as *c_char,
text.len() as c_int,
str::as_buf(run.text, |ctext: *u8, _l: uint| {
hb_buffer_add_utf8(hb_buffer,
ctext as *c_char,
run.text.len() as c_int,
0 as c_uint,
text.len() as c_int);
run.text.len() as c_int);
});
hb_shape(hbfont, buffer, null(), 0 as c_uint);
hb_shape(hb_font, hb_buffer, null(), 0 as c_uint);
let info_len = 0 as c_uint;
let info_ = hb_buffer_get_glyph_infos(buffer, to_unsafe_ptr(&info_len));
assert info_.is_not_null();
let pos_len = 0 as c_uint;
let pos = hb_buffer_get_glyph_positions(buffer, to_unsafe_ptr(&pos_len));
assert pos.is_not_null();
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_len == pos_len;
assert info_buf_len == pos_buf_len;
let mut glyphs = ~[];
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(fixed_to_float_hb(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(fixed_to_float_hb(x)),
au::from_frac_px(fixed_to_float_hb(y))))
};
// TODO: convert pos.y_advance into offset adjustment
// TODO: handle multiple glyphs per char, ligatures, etc.
// See Issue #
debug!("glyph %?: index %?, advance %?, offset %?",
i, codepoint, advance, offset);
for uint::range(0u, info_len as uint) |i| {
let info_ = offset(info_, i);
let pos = offset(pos, i);
let codepoint = (*info_).codepoint as u32;
let pos = hb_glyph_pos_to_servo_glyph_pos(&*pos);
#debug("glyph %?: codep %?, x_adv %?, y_adv %?, x_off %?, y_of %?",
i, codepoint, pos.advance.x, pos.advance.y, pos.offset.x, pos.offset.y);
let data = GlyphData(codepoint, advance, offset, false, false, false);
run.glyphs.add_glyph_for_index(i, &data);
} /* unsafe */ }
glyphs += ~[Glyph(codepoint, pos)];
}
hb_buffer_destroy(buffer);
hb_buffer_destroy(hb_buffer);
hb_font_funcs_destroy(funcs);
hb_font_destroy(hbfont);
hb_face_destroy(hbface);
hb_font_destroy(hb_font);
hb_face_destroy(hb_face);
hb_blob_destroy(face_blob);
return glyphs;
}
extern fn glyph_func(_font: *hb_font_t,
@ -111,17 +140,12 @@ extern fn glyph_func(_font: *hb_font_t,
glyph: *mut hb_codepoint_t,
_user_data: *c_void) -> hb_bool_t unsafe {
let font: *Font = reinterpret_cast(&font_data);
let font: *Font = cast::transmute(font_data);
assert font.is_not_null();
return match (*font).glyph_index(unicode as char) {
Some(g) => {
*glyph = g as hb_codepoint_t;
true
}
None => {
false
}
Some(g) => { *glyph = g as hb_codepoint_t; true },
None => false
} as hb_bool_t;
}
@ -129,19 +153,11 @@ 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 = reinterpret_cast(&font_data);
let font: *Font = cast::transmute(font_data);
assert font.is_not_null();
let h_advance = (*font).glyph_h_advance(glyph as u32);
#debug("h_advance for codepoint %? is %?", glyph, h_advance);
return h_advance as hb_position_t;
}
fn hb_glyph_pos_to_servo_glyph_pos(hb_pos: &hb_glyph_position_t) -> GlyphPos {
GlyphPos(Point2D(au::from_px(hb_pos.x_advance as int),
au::from_px(hb_pos.y_advance as int)),
Point2D(au::from_px(hb_pos.x_offset as int),
au::from_px(hb_pos.y_offset as int)))
let advance = (*font).glyph_h_advance(glyph as GlyphIndex);
float_to_fixed_hb(advance)
}
fn should_get_glyph_indexes() {

View file

@ -1,138 +1,137 @@
use arc = std::arc;
use arc::ARC;
use au = gfx::geometry;
use font::Font;
use font_cache::FontCache;
use geom::point::Point2D;
use geom::size::Size2D;
use gfx::geometry::au;
use glyph::GlyphStore;
use layout::context::LayoutContext;
use libc::{c_void};
use font_cache::FontCache;
use font::Font;
use glyph::Glyph;
use shaper::shape_text;
use servo_util::color;
use shaper::shape_textrun;
use std::arc;
/// A single, unbroken line of text
struct TextRun {
pub struct TextRun {
text: ~str,
priv glyphs: ~[Glyph],
priv size_: Size2D<au>,
priv min_break_width_: au,
priv glyphs: GlyphStore,
}
impl TextRun {
/// The size of the entire TextRun
pure fn size() -> Size2D<au> { self.size_ }
pure fn min_break_width() -> au { self.min_break_width_ }
pure fn glyphs(&self) -> &self/GlyphStore { &self.glyphs }
/// Split a run of text in two
// FIXME: Should be storing a reference to the Font inside
// of the TextRun, but I'm hitting cycle collector bugs
fn split(font: &Font, h_offset: au) -> (TextRun, TextRun) {
assert h_offset >= self.min_break_width();
assert h_offset <= self.size_.width;
fn min_width_for_range(ctx: &LayoutContext, offset: uint, length: uint) -> au {
assert length > 0;
assert offset < self.text.len();
assert offset + length <= self.text.len();
let mut curr_run = ~"";
let mut max_piece_width = au(0);
// TODO: use a real font reference
let font = ctx.font_cache.get_test_font();
for self.iter_indivisible_pieces_for_range(offset, length) |piece_offset, piece_len| {
for iter_indivisible_slices(font, self.text) |slice| {
let mut candidate = copy curr_run;
if candidate.is_not_empty() {
str::push_str(&mut candidate, " "); // FIXME: just inserting spaces between words can't be right
}
str::push_str(&mut candidate, slice);
let glyphs = shape_text(font, candidate);
let size = glyph_run_size(glyphs);
if size.width <= h_offset {
curr_run = move candidate;
} else {
break;
}
}
assert curr_run.is_not_empty();
let first = move curr_run;
let second_start = match str::find_from(self.text, first.len(), |c| !char::is_whitespace(c)) {
Some(idx) => idx,
None => {
// This will be an empty string
self.text.len()
let metrics = font.measure_text(&self, piece_offset, piece_len);
if metrics.advance > max_piece_width {
max_piece_width = metrics.advance;
}
};
let second = str::slice(self.text, second_start, self.text.len());
return (TextRun(font, first), TextRun(font, second));
return max_piece_width;
}
}
fn TextRun(font: &Font, +text: ~str) -> TextRun {
let glyphs = shape_text(font, text);
let size = glyph_run_size(glyphs);
let min_break_width = calc_min_break_width(font, text);
fn iter_natural_lines_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool) {
assert length > 0;
assert offset < self.text.len();
assert offset + length <= self.text.len();
TextRun {
text: text,
glyphs: shape_text(font, text),
size_: size,
min_break_width_: min_break_width
}
}
let mut clump_start = offset;
let mut clump_end = offset;
let mut in_clump = false;
fn glyph_run_size(glyphs: &[Glyph]) -> Size2D<au> {
let height = au::from_px(20);
let pen_start_x = au::from_px(0);
let pen_start_y = height;
let pen_start = Point2D(pen_start_x, pen_start_y);
let pen_end = glyphs.foldl(pen_start, |cur, glyph| {
Point2D(cur.x.add(&glyph.pos.offset.x).add(&glyph.pos.advance.x),
cur.y.add(&glyph.pos.offset.y).add(&glyph.pos.advance.y))
});
return Size2D(pen_end.x, pen_end.y);
}
/// Discovers the width of the largest indivisible substring
fn calc_min_break_width(font: &Font, text: &str) -> au {
let mut max_piece_width = au(0);
for iter_indivisible_slices(font, text) |slice| {
let glyphs = shape_text(font, slice);
let size = glyph_run_size(glyphs);
if size.width > max_piece_width {
max_piece_width = size.width
// clump non-linebreaks of nonzero length
for uint::range(offset, offset + length) |i| {
match (self.glyphs.char_is_newline(i), in_clump) {
(false, true) => { clump_end = i; }
(false, false) => { in_clump = true; clump_start = i; clump_end = i; }
(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_start, clump_end - clump_start + 1) { break }
}
}
}
// flush any remaining chars as a line
if in_clump {
clump_end = offset + length - 1;
f(clump_start, clump_end - clump_start + 1);
}
}
return max_piece_width;
pure fn iter_indivisible_pieces_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool) {
assert length > 0;
assert offset < self.text.len();
assert offset + length <= self.text.len();
//TODO: need a more sophisticated model of words and possible breaks
let text = str::view(self.text, offset, length);
let mut clump_start = offset;
loop {
// clump contiguous non-whitespace
match str::find_from(text, clump_start, |c| !char::is_whitespace(c)) {
Some(clump_end) => {
if !f(clump_start, clump_end - clump_start + 1) { break }
clump_start = clump_end + 1;
// reached end
if clump_start == offset + length { break }
},
None => {
// nothing left, flush last piece containing only spaces
if clump_start < offset + length {
let clump_end = offset + length - 1;
f(clump_start, clump_end - clump_start + 1);
}
break
}
};
// clump contiguous whitespace
match str::find_from(text, clump_start, |c| char::is_whitespace(c)) {
Some(clump_end) => {
if !f(clump_start, clump_end - clump_start + 1) { break }
clump_start = clump_end + 1;
// reached end
if clump_start == offset + length { break }
}
None => {
// nothing left, flush last piece containing only spaces
if clump_start < offset + length {
let clump_end = offset + length - 1;
f(clump_start, clump_end - clump_start + 1);
}
break
}
}
}
}
}
fn TextRun(font: &Font, text: ~str) -> TextRun {
let glyph_store = GlyphStore(text);
let run = TextRun {
text: text,
glyphs: glyph_store,
};
shape_textrun(font, &run);
return run;
}
/// Iterates over all the indivisible substrings
fn iter_indivisible_slices(_font: &Font, text: &r/str,
f: fn((&r/str)) -> bool) {
let mut curr = text;
loop {
match str::find(curr, |c| !char::is_whitespace(c) ) {
Some(idx) => {
curr = str::view(curr, idx, curr.len());
}
None => {
// Everything else is whitespace
break
}
}
match str::find(curr, |c| char::is_whitespace(c) ) {
Some(idx) => {
let piece = str::view(curr, 0, idx);
if !f(piece) { break }
curr = str::view(curr, idx, curr.len());
}
None => {
assert curr.is_not_empty();
if !f(curr) { break }
// This is the end of the string
break;
}
}
}
}
#[test]
fn test_calc_min_break_width1() {
let flib = FontCache();

View file

@ -1,7 +1,22 @@
export true_type_tag;
pub fn float_to_fixed(before: int, f: float) -> i32 {
(1i32 << before) * (f as i32)
}
#[doc = "Generate a 32-bit TrueType tag from its 4 charecters"]
fn true_type_tag(a: char, b: char, c: char, d: char) -> u32 {
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 charecters */
pub fn true_type_tag(a: char, b: char, c: char, d: char) -> u32 {
(a << 24 | b << 16 | c << 8 | d) as u32
}