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 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 gfx::geometry::*;
use geom::rect::Rect; use geom::rect::Rect;
use image::base::Image; use image::base::Image;
@ -7,30 +7,24 @@ use render_task::RenderContext;
use std::arc::{ARC, clone}; use std::arc::{ARC, clone};
use dvec::DVec; use dvec::DVec;
use text::glyph::Glyph; use text::text_run::TextRun;
pub use layout::display_list_builder::DisplayListBuilder; pub use layout::display_list_builder::DisplayListBuilder;
// TODO: invert this so common data is nested inside each variant as first arg.
struct DisplayItem { struct DisplayItem {
draw: ~fn((&DisplayItem), (&RenderContext)), draw: ~fn((&DisplayItem), (&RenderContext)),
bounds : Rect<au>, // TODO: whose coordinate system should this use? bounds : Rect<au>, // TODO: whose coordinate system should this use?
data : DisplayItemData data : DisplayItemData
} }
enum DisplayItemData { pub enum DisplayItemData {
SolidColorData(u8, u8, u8), 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>), 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) { 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 { match self.data {
GlyphData(run) => draw_glyphs(ctx, self.bounds, &run), TextData(~run, offset, len) => draw_text(ctx, self.bounds, &run, offset, len),
_ => fail _ => 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 { pub fn SolidColor(bounds: Rect<au>, r: u8, g: u8, b: u8) -> DisplayItem {
DisplayItem { DisplayItem {
// TODO: this seems wrong.
draw: |self, ctx| draw_SolidColor(self, ctx), draw: |self, ctx| draw_SolidColor(self, ctx),
bounds: bounds, bounds: bounds,
data: SolidColorData(r, g, b) 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 { DisplayItem {
draw: |self, ctx| draw_Glyphs(self, ctx), draw: |self, ctx| draw_Text(self, ctx),
bounds: bounds, bounds: bounds,
data: GlyphData(run) data: TextData(run, offset, length)
} }
} }
// ARC should be cloned into ImageData, but Images are not sendable // ARC should be cloned into ImageData, but Images are not sendable
pub fn Image(bounds: Rect<au>, image: ARC<~image::base::Image>) -> DisplayItem { pub fn Image(bounds: Rect<au>, image: ARC<~image::base::Image>) -> DisplayItem {
DisplayItem { DisplayItem {
// TODO: this seems wrong.
draw: |self, ctx| draw_Image(self, ctx), draw: |self, ctx| draw_Image(self, ctx),
bounds: bounds, bounds: bounds,
data: ImageData(clone(&image)) 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)) Rect(Point2D(x, y), Size2D(w, h))
} }
pub fn zero_rect() -> Rect<au> { pub pure fn zero_rect() -> Rect<au> {
let z = au(0); let z = au(0);
Rect(Point2D(z, z), Size2D(z, z)) Rect(Point2D(z, z), Size2D(z, z))
} }
pub pure fn zero_point() -> Point2D<au> {
pub fn zero_point() -> Point2D<au> {
Point2D(au(0), au(0)) Point2D(au(0), au(0))
} }
pub fn zero_size() -> Size2D<au> { pub pure fn zero_size() -> Size2D<au> {
Size2D(au(0), au(0)) 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 { pub pure fn from_px(i: int) -> au {
from_int(i * 60) from_int(i * 60)
} }
@ -60,3 +63,8 @@ pub pure fn from_px(i: int) -> au {
pub pure fn to_px(au: au) -> int { pub pure fn to_px(au: au) -> int {
(*au / 60) as 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 mod azure::azure_hl;
use au = geometry;
use au = gfx::geometry;
use au::au; 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::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::{AsAzureRect, B8G8R8A8, Color, ColorPattern, DrawOptions, DrawSurfaceOptions};
use azure_hl::{DrawTarget, Linear}; 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 ptr::to_unsafe_ptr;
use std::arc::ARC; use std::arc::ARC;
use azure::cairo::{cairo_font_face_t, cairo_scaled_font_t};
use std::cell::Cell; use std::cell::Cell;
use compositor::Compositor; use text::text_run::TextRun;
use servo_text::font_cache::FontCache; use text::font::Font;
use text::font_cache::FontCache;
use pipes::{Port, Chan};
pub type Renderer = comm::Chan<Msg>; pub type Renderer = comm::Chan<Msg>;
@ -133,7 +136,7 @@ pub fn draw_image(ctx: &RenderContext, bounds: Rect<au>, image: ARC<~Image>) {
draw_options); 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 ptr::{null};
use vec::raw::to_ptr; use vec::raw::to_ptr;
use libc::types::common::c99::{uint16_t, uint32_t}; 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}; AzReleaseColorPattern};
use azure::cairo::bindgen::cairo_scaled_font_destroy; 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 font = ctx.font_cache.get_test_font();
let nfont: AzNativeFont = { 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 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 = { let azglyph: AzGlyph = {
mIndex: glyph.index as uint32_t, mIndex: glyph.index() as uint32_t,
mPosition: { mPosition: {
x: au::to_px(origin.x.add(&glyph.pos.offset.x)) as AzFloat, x: au::to_px(origin.x + glyph_offset.x) as AzFloat,
y: au::to_px(origin.y.add(&glyph.pos.offset.y)) as AzFloat y: au::to_px(origin.y + glyph_offset.y) as AzFloat
} }
}; };
origin = Point2D(origin.x.add(&glyph.pos.advance.x), origin = Point2D(origin.x + glyph_advance, origin.y);
origin.y.add(&glyph.pos.advance.y)); azglyphs.push(move azglyph)
azglyph };
});
let azglyph_buf_len = azglyphs.len();
let azglyph_buf = dvec::unwrap(move azglyphs);
let glyphbuf: AzGlyphBuffer = unsafe {{ let glyphbuf: AzGlyphBuffer = unsafe {{
mGlyphs: to_ptr(azglyphs), mGlyphs: to_ptr(azglyph_buf),
mNumGlyphs: azglyphs.len() as uint32_t mNumGlyphs: azglyph_buf_len as uint32_t
}}; }};
// TODO: this call needs to move into azure_hl.rs // TODO: this call needs to move into azure_hl.rs

View file

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

View file

@ -93,7 +93,8 @@ enum RenderBoxType {
pub enum RenderBox { pub enum RenderBox {
GenericBox(RenderBoxData), GenericBox(RenderBoxData),
ImageBox(RenderBoxData, ImageHolder), ImageBox(RenderBoxData, ImageHolder),
TextBox(RenderBoxData, TextBoxData) TextBox(RenderBoxData, TextBoxData),
UnscannedTextBox(RenderBoxData, ~str)
} }
impl RenderBox { impl RenderBox {
@ -101,7 +102,8 @@ impl RenderBox {
match *self { match *self {
GenericBox(ref d) => d, GenericBox(ref d) => d,
ImageBox(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 * may cause glyphs to be allocated. For now, it's impure because of
* holder.get_image() * holder.get_image()
*/ */
fn get_min_width() -> au { fn get_min_width(ctx: &LayoutContext) -> au {
match self { match self {
// TODO: this should account for min/pref widths of the // TODO: this should account for min/pref widths of the
// box element in isolation. That includes // box element in isolation. That includes
@ -141,13 +143,12 @@ impl RenderBox {
// TODO: consult CSS 'width', margin, border. // TODO: consult CSS 'width', margin, border.
// TODO: If image isn't available, consult 'width'. // TODO: If image isn't available, consult 'width'.
ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width), ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width),
TextBox(_,d) => d.runs.foldl(au(0), |sum, run| { TextBox(_,d) => d.run.min_width_for_range(ctx, d.offset, d.length),
au::max(*sum, run.min_break_width()) UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here."
})
} }
} }
fn get_pref_width() -> au { fn get_pref_width(_ctx: &LayoutContext) -> au {
match self { match self {
// TODO: this should account for min/pref widths of the // TODO: this should account for min/pref widths of the
// box element in isolation. That includes // box element in isolation. That includes
@ -156,12 +157,30 @@ impl RenderBox {
// that of its children to arrive at the context width. // that of its children to arrive at the context width.
GenericBox(*) => au(0), GenericBox(*) => au(0),
ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width), ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width),
// TODO: account for line breaks, etc. The run should know
// how to compute its own min and pref widths, and should // a text box cannot span lines, so assume that this is an unsplit text box.
// probably cache them.
TextBox(_,d) => d.runs.foldl(au(0), |sum, run| { // TODO: If text boxes have been split to wrap lines, then
au::max(*sum, run.size().width) // 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(*) => { TextBox(*) => {
copy self.d().position copy self.d().position
} },
UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here."
} }
} }
@ -251,25 +271,9 @@ impl RenderBox {
copy self.d().position.size); copy self.d().position.size);
match self { match self {
UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here.",
TextBox(_,d) => { TextBox(_,d) => {
let mut runs = d.runs; list.push(~dl::Text(bounds, ~(copy *d.run), d.offset, d.length))
// 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
}
}
}, },
// TODO: items for background, border, outline // TODO: items for background, border, outline
GenericBox(*) => { }, GenericBox(*) => { },
@ -348,12 +352,8 @@ impl RenderBox : BoxedDebugMethods {
let repr = match self { let repr = match self {
@GenericBox(*) => ~"GenericBox", @GenericBox(*) => ~"GenericBox",
@ImageBox(*) => ~"ImageBox", @ImageBox(*) => ~"ImageBox",
@TextBox(_,d) => { @TextBox(_,d) => fmt!("TextBox(text=%s)", str::substr(d.run.text, d.offset, d.length)),
let mut s = d.runs.foldl(~"TextBox(runs=", |s, run| { @UnscannedTextBox(_,s) => fmt!("UnscannedTextBox(%s)", s)
fmt!("%s \"%s\"", *s, run.text)
});
s += ~")"; s
}
}; };
fmt!("box b%?: %?", self.d().id, repr) fmt!("box b%?: %?", self.d().id, repr)

View file

@ -13,10 +13,7 @@ use layout::context::LayoutContext;
use layout::flow::*; use layout::flow::*;
use layout::inline::InlineFlowData; use layout::inline::InlineFlowData;
use layout::root::RootFlowData; use layout::root::RootFlowData;
use layout::text::TextBoxData;
use option::is_none; use option::is_none;
use servo_text::font_cache::FontCache;
use servo_text::text_run::TextRun;
use util::tree; use util::tree;
export LayoutTreeBuilder; 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| { do node.read |n| {
match n.kind { match n.kind {
~Text(string) => { ~Text(string) => @UnscannedTextBox(RenderBoxData(node, ctx, self.next_box_id()), copy 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]))
},
_ => fail ~"WAT error: why couldn't we make a text box?" _ => 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::rect::Rect;
use geom::size::Size2D; use geom::size::Size2D;
use gfx::geometry::au; use gfx::geometry::au;
use layout::box::{RenderBox, RenderBoxTree, ImageBox, TextBox, GenericBox}; use layout::box::{RenderBox, RenderBoxTree, ImageBox, TextBox, GenericBox, UnscannedTextBox};
use layout::flow::{FlowContext, InlineFlow};
use layout::context::LayoutContext; use layout::context::LayoutContext;
use layout::flow::{FlowContext, InlineFlow};
use layout::text::TextBoxData;
use num::Num; use num::Num;
use servo_text::text_run::TextRun;
use std::arc;
use util::tree; use util::tree;
/* /*
@ -40,10 +43,24 @@ hard to try out that alternative.
type BoxRange = {start: u8, len: u8}; 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 { 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. // correspond to one Node/Element.
boxes: DList<@RenderBox>, boxes: DVec<@RenderBox>,
// vec of ranges into boxes that represents line positions. // vec of ranges into boxes that represents line positions.
// these ranges are disjoint, and are the result of inline layout. // these ranges are disjoint, and are the result of inline layout.
lines: DVec<BoxRange>, lines: DVec<BoxRange>,
@ -55,7 +72,7 @@ struct InlineFlowData {
fn InlineFlowData() -> InlineFlowData { fn InlineFlowData() -> InlineFlowData {
InlineFlowData { InlineFlowData {
boxes: DList(), boxes: DVec(),
lines: DVec(), lines: DVec(),
elems: DVec() elems: DVec()
} }
@ -73,15 +90,18 @@ trait InlineLayout {
impl FlowContext : InlineLayout { impl FlowContext : InlineLayout {
pure fn starts_inline_flow() -> bool { match self { InlineFlow(*) => true, _ => false } } 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(); 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 min_width = au(0);
let mut pref_width = au(0); let mut pref_width = au(0);
for self.inline().boxes.each |box| { for self.inline().boxes.each |box| {
min_width = au::max(min_width, box.get_min_width()); min_width = au::max(min_width, box.get_min_width(ctx));
pref_width = au::max(pref_width, box.get_pref_width()); pref_width = au::max(pref_width, box.get_pref_width(ctx));
} }
self.d().min_width = min_width; self.d().min_width = min_width;
@ -101,6 +121,9 @@ impl FlowContext : InlineLayout {
//let mut cur_x = au(0); //let mut cur_x = au(0);
let mut cur_y = 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| { for self.inline().boxes.each |box| {
/* TODO: actually do inline flow. /* TODO: actually do inline flow.
- Create a working linebox, and successively put boxes - Create a working linebox, and successively put boxes
@ -111,25 +134,26 @@ impl FlowContext : InlineLayout {
- Save the dvec of this context's lineboxes. */ - 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 { box.d().position.size.width = match *box {
@ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).width), @ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).width),
@TextBox(_,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 // 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 { box.d().position.size.height = match *box {
@ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).height), @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 // 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); 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 // 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 // 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| { for self.inline().boxes.each |box| {
box.build_display_list(builder, dirty, offset, list) box.build_display_list(builder, dirty, offset, list)
} }

View file

@ -1,30 +1,28 @@
/** Text layout. */ /** Text layout. */
use au = gfx::geometry; use au = gfx::geometry;
use au::au;
use geom::size::Size2D; use geom::size::Size2D;
use gfx::geometry::au;
use servo_text::text_run::TextRun; use servo_text::text_run::TextRun;
use servo_text::font_cache::FontCache; use servo_text::font_cache::FontCache;
use layout::box::{TextBox, RenderBox}; use layout::box::{TextBox, RenderBox};
use layout::context::LayoutContext; use layout::context::LayoutContext;
struct TextBoxData { pub struct TextBoxData {
text: ~str, run: @TextRun,
mut runs: ~[TextRun] offset: uint,
length: uint
} }
fn TextBoxData(text: ~str, runs: ~[TextRun]) -> TextBoxData { pub fn TextBoxData(run: @TextRun, offset: uint, length: uint) -> TextBoxData {
TextBoxData { TextBoxData {
text: text, run: run,
runs: runs offset: offset,
length: length
} }
} }
trait TextLayout { /* The main reflow routine for text layout.
fn reflow_text(ctx: &LayoutContext);
}
/** The main reflow routine for text layout. */
impl @RenderBox : TextLayout { impl @RenderBox : TextLayout {
fn reflow_text(ctx: &LayoutContext) { fn reflow_text(ctx: &LayoutContext) {
let d = match self { let d = match self {
@ -32,6 +30,7 @@ impl @RenderBox : TextLayout {
_ => { fail ~"expected text box in reflow_text!" } _ => { fail ~"expected text box in reflow_text!" }
}; };
// TODO: get font from textrun's TextStyle
let font = ctx.font_cache.get_test_font(); let font = ctx.font_cache.get_test_font();
// Do line breaking. // Do line breaking.
@ -76,26 +75,4 @@ impl @RenderBox : TextLayout {
self.d().position.size = Size2D(max_width, total_height); self.d().position.size = Size2D(max_width, total_height);
d.runs = move dvec::unwrap(lines); 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; 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 glyph::GlyphIndex;
use vec_to_ptr = vec::raw::to_ptr;
use libc::{ c_int, c_double, c_ulong }; use libc::{ c_int, c_double, c_ulong };
use ptr::{ null, addr_of };
use native_font::NativeFont; 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 A font handle. Layout can use this to calculate glyph metrics
@ -16,11 +21,38 @@ struct Font {
lib: @FontCache, lib: @FontCache,
fontbuf: @~[u8], fontbuf: @~[u8],
native_font: NativeFont, native_font: NativeFont,
metrics: FontMetrics metrics: FontMetrics,
} }
impl Font { struct RunMetrics {
fn buf() -> @~[u8] { 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 self.fontbuf
} }
@ -28,14 +60,16 @@ impl Font {
self.native_font.glyph_index(codepoint) 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) { match self.native_font.glyph_h_advance(glyph) {
Some(adv) => adv, 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 { fn Font(lib: @FontCache, fontbuf: @~[u8], +native_font: NativeFont, +metrics: FontMetrics) -> Font {
Font { Font {
lib: lib, 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 { struct FontMetrics {
underline_size: float, underline_size: float,
underline_offset: float, underline_offset: float,
leading: float, leading: float,
x_height: float, x_height: float,
// how many appunits an em is equivalent to (based on point-to-au)
em_size: au,
em_height: float, em_height: float,
em_ascent: float, em_ascent: float,
em_descent: float, em_descent: float,

View file

@ -2,22 +2,57 @@ use au = gfx::geometry;
use au::au; use au::au;
use core::cmp::{Ord, Eq}; use core::cmp::{Ord, Eq};
use core::dvec::DVec; use core::dvec::DVec;
use core::u16;
use geom::point::Point2D; use geom::point::Point2D;
use std::sort; use std::sort;
use servo_util::vec::*; use servo_util::vec::*;
use num::from_int;
export GlyphIndex, GlyphPos, Glyph; // GlyphEntry is a port of Gecko's CompressedGlyph scheme for storing
// glyph data compactly.
struct CompressedGlyph { //
mut value : u32 // 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 /// The index of a particular glyph within a font
type GlyphIndex = u32; type GlyphIndex = u32;
// TODO: unify with bit flags?
enum BreakType {
BreakTypeNone,
BreakTypeNormal,
BreakTypeHyphen
}
const BREAK_TYPE_NONE : u8 = 0x0u8; const BREAK_TYPE_NONE : u8 = 0x0u8;
const BREAK_TYPE_NORMAL : u8 = 0x1u8; 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. // 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, // Non-simple glyphs (more than one glyph per char; missing glyph,
// newline, tab, large advance, or nonzero x/y offsets) may have one // newline, tab, large advance, or nonzero x/y offsets) may have one
// or more detailed glyphs associated with them. They are stored in a // 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. // unicode char.
// The number of detailed glyphs for this char. If the char couldn't // 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; type DetailedGlyphCount = u16;
enum GlyphStoreResult<T> { pure fn InitialGlyphEntry() -> GlyphEntry {
Simple(T), GlyphEntry { value: 0 }
Detailed(u32)
} }
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_glyph_id(index);
assert is_simple_advance(advance); assert is_simple_advance(advance);
let index_mask = index as u32; let index_mask = index as u32;
let advance_mask = (*advance as u32) << GLYPH_ADVANCE_SHIFT; let advance_mask = (*advance as u32) << GLYPH_ADVANCE_SHIFT;
CompressedGlyph { GlyphEntry {
value: index_mask | advance_mask | FLAG_IS_SIMPLE_GLYPH 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; let mut val = FLAG_NOT_MISSING;
if !startsCluster { if !startsCluster {
@ -93,42 +132,47 @@ fn ComplexGlyph(startsCluster: bool, startsLigature: bool, glyphCount: u16) -> C
} }
val |= (glyphCount as u32) << GLYPH_COUNT_SHIFT; val |= (glyphCount as u32) << GLYPH_COUNT_SHIFT;
CompressedGlyph { GlyphEntry {
value: val value: val
} }
} }
fn MissingGlyphs(glyphCount: u16) -> CompressedGlyph { // Create a GlyphEntry for the case where glyphs couldn't be found
CompressedGlyph { // 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 value: (glyphCount as u32) << GLYPH_COUNT_SHIFT
} }
} }
impl CompressedGlyph { // Getters and setters for GlyphEntry. Setter methods are functional,
pure fn advance() -> GlyphStoreResult<au> { // because GlyphEntry is immutable and only a u32 in size.
match self.is_simple() { impl GlyphEntry {
true => Simple(num::from_int(((self.value & GLYPH_ADVANCE_MASK) >> GLYPH_ADVANCE_SHIFT) as int)), // getter methods
false => Detailed(self.glyph_count()) 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> { pure fn index() -> GlyphIndex {
match self.is_simple() { assert self.is_simple();
true => Simple(self.value & GLYPH_ID_MASK), self.value & GLYPH_ID_MASK
false => Detailed(self.glyph_count())
}
} }
pure fn offset() -> GlyphStoreResult<Point2D<au>> { pure fn offset() -> Point2D<au> {
match self.is_simple() { assert self.is_simple();
true => Simple(Point2D(au(0), au(0))), Point2D(au(0), au(0))
false => Detailed(self.glyph_count())
}
} }
// 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 // True if original char was normal (U+0020) space. Other chars may
// map to space glyph, but this does not account for them. // 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) !self.is_simple() && self.has_flag(FLAG_CHAR_IS_NEWLINE)
} }
// TODO: make typesafe break enum pure fn can_break_before() -> BreakType {
pure fn can_break_before() -> u8 { let flag = ((self.value & FLAG_CAN_BREAK_MASK) >> FLAG_CAN_BREAK_SHIFT) as u8;
((self.value & FLAG_CAN_BREAK_MASK) >> FLAG_CAN_BREAK_SHIFT) as u8 break_flag_to_enum(flag)
} }
// setter methods // setter methods
pure fn set_char_is_space() -> GlyphEntry {
fn set_is_space() { GlyphEntry(self.value | FLAG_CHAR_IS_SPACE)
self.value |= FLAG_CHAR_IS_SPACE;
} }
fn set_is_tab() { pure fn set_char_is_tab() -> GlyphEntry {
assert !self.is_simple(); 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(); assert !self.is_simple();
self.value |= FLAG_CHAR_IS_NEWLINE; GlyphEntry(self.value | FLAG_CHAR_IS_NEWLINE)
} }
// returns whether the setting had changed. // returns a glyph entry only if the setting had changed.
fn set_can_break_before(flags: u8) -> bool { pure fn set_can_break_before(e: BreakType) -> Option<GlyphEntry> {
assert flags <= 0x2; let flag = break_enum_to_flag(e);
let mask = (flags as u32) << FLAG_CAN_BREAK_SHIFT; let mask = (flag as u32) << FLAG_CAN_BREAK_SHIFT;
let toggle = mask ^ (self.value & FLAG_CAN_BREAK_MASK); 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 // helper methods
/*priv*/ pure fn glyph_count() -> u32 { /*priv*/ pure fn glyph_count() -> u16 {
assert !self.is_simple(); 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 { pure fn is_simple() -> bool {
(self.value & FLAG_IS_SIMPLE_GLYPH) == self.value self.has_flag(FLAG_IS_SIMPLE_GLYPH)
} }
/*priv*/ pure fn has_flag(flag: u32) -> bool { /*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 { struct DetailedGlyph {
// The GlyphIndex, or the unicode codepoint if glyph is missing. index: GlyphIndex,
index: u32,
// glyph's advance, in the text's direction (RTL or RTL) // glyph's advance, in the text's direction (RTL or RTL)
advance: au, advance: au,
// glyph's offset from the font's em-box (from top-left) // glyph's offset from the font's em-box (from top-left)
offset: Point2D<au> 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 { struct DetailedGlyphRecord {
// source character/CompressedGlyph offset in the TextRun // source string offset/GlyphEntry offset in the TextRun
cg_offset: u32, entry_offset: uint,
// offset into the detailed glyphs buffer // offset into the detailed glyphs buffer
dg_offset: uint detail_offset: uint
} }
impl DetailedGlyphRecord : Ord { impl DetailedGlyphRecord : Ord {
pure fn lt(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.cg_offset <= other.cg_offset } pure fn le(other: &DetailedGlyphRecord) -> bool { self.entry_offset <= other.entry_offset }
pure fn ge(other: &DetailedGlyphRecord) -> bool { self.cg_offset >= other.cg_offset } pure fn ge(other: &DetailedGlyphRecord) -> bool { self.entry_offset >= other.entry_offset }
pure fn gt(other: &DetailedGlyphRecord) -> bool { self.cg_offset > other.cg_offset } pure fn gt(other: &DetailedGlyphRecord) -> bool { self.entry_offset > other.entry_offset }
} }
impl DetailedGlyphRecord : Eq { impl DetailedGlyphRecord : Eq {
pure fn eq(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.cg_offset != other.cg_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 { struct DetailedGlyphStore {
dg_buffer: DVec<DetailedGlyph>, detail_buffer: DVec<DetailedGlyph>,
dg_lookup: DVec<DetailedGlyphRecord>, detail_lookup: DVec<DetailedGlyphRecord>,
mut lookup_is_sorted: bool, mut lookup_is_sorted: bool,
} }
fn DetailedGlyphStore() -> DetailedGlyphStore { fn DetailedGlyphStore() -> DetailedGlyphStore {
DetailedGlyphStore { DetailedGlyphStore {
dg_buffer: DVec(), detail_buffer: DVec(),
dg_lookup: DVec(), detail_lookup: DVec(),
lookup_is_sorted: false lookup_is_sorted: false
} }
} }
impl DetailedGlyphStore { 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 { let entry = DetailedGlyphRecord {
cg_offset: cg_offset, entry_offset: entry_offset,
dg_offset: self.dg_buffer.len() detail_offset: self.detail_buffer.len()
}; };
/* /* TODO: don't actually assert this until asserts are compiled
TODO: don't actually assert this until asserts are compiled in/out based on severity, debug/release, etc. This assertion
in/out based on severity, debug/release, etc. would wreck the complexity of the lookup.
See Rust Issue #3647, #2228, #3627 for related information. 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) assert !arr.contains(entry)
} }
*/ */
self.dg_lookup.push(entry); self.detail_lookup.push(entry);
self.dg_buffer.push_all(glyphs); self.detail_buffer.push_all(glyphs);
self.lookup_is_sorted = false; self.lookup_is_sorted = false;
} }
// not pure; may perform a deferred sort. // not pure; may perform a deferred sort.
fn get_glyphs_for_cg(&self, cg_offset: u32, count: uint) -> &[DetailedGlyph] { fn get_detailed_glyphs_for_entry(&self, entry_offset: uint, count: u16) -> &[DetailedGlyph] {
assert count > 0 && count < self.dg_buffer.len(); assert count > 0;
assert (count as uint) <= self.detail_buffer.len();
self.ensure_sorted(); self.ensure_sorted();
let key = DetailedGlyphRecord { let key = DetailedGlyphRecord {
cg_offset: cg_offset, entry_offset: entry_offset,
dg_offset: 0 // unused detail_offset: 0 // unused
}; };
do self.dg_lookup.borrow |records : &[DetailedGlyphRecord]| { do self.detail_lookup.borrow |records : &[DetailedGlyphRecord]| {
match records.binary_search_index(&key) { match records.binary_search_index(&key) {
None => fail ~"Invalid index not found in detailed glyph lookup table!", None => fail ~"Invalid index not found in detailed glyph lookup table!",
Some(i) => { Some(i) => {
do self.dg_buffer.borrow |glyphs : &[DetailedGlyph]| { do self.detail_buffer.borrow |glyphs : &[DetailedGlyph]| {
assert i + count < glyphs.len(); assert i + (count as uint) < glyphs.len();
// return a view into the buffer // 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; return;
} }
do self.dg_lookup.borrow_mut |arr| { do self.detail_lookup.borrow_mut |arr| {
sort::quick_sort3(arr); sort::quick_sort3(arr);
}; };
self.lookup_is_sorted = true; 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 { struct GlyphStore {
mut cg_buffer: ~[CompressedGlyph], // we use a DVec here instead of a mut vec, since this is much safer.
dg_store: DetailedGlyphStore, entry_buffer: DVec<GlyphEntry>,
detail_store: DetailedGlyphStore,
} }
// Initializes the glyph store, but doesn't actually shape anything. // Initializes the glyph store, but doesn't actually shape anything.
// Use the set_glyph, set_glyphs() methods to store glyph data. // 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) -> GlyphStore {
fn GlyphStore(_text: ~str) { assert text.len() > 0;
let buffer = vec::from_elem(text.len(), InitialGlyphEntry());
GlyphStore {
entry_buffer: dvec::from_vec(buffer),
detail_store: DetailedGlyphStore(),
}
} }
impl GlyphStore { 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. */ assert i < self.entry_buffer.len();
struct GlyphPos {
advance: Point2D<au>,
offset: Point2D<au>,
}
fn GlyphPos(advance: Point2D<au>, offset: Point2D<au>) -> GlyphPos { let entry = match (data.is_missing, glyph_is_compressible(data)) {
GlyphPos { (true, _) => MissingGlyphsEntry(1),
advance : advance, (false, true) => { SimpleGlyphEntry(data.index, data.advance) },
offset : offset, (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)
/** A single glyph. */ }
struct Glyph { };
index: u32,
pos: GlyphPos, self.entry_buffer.set_elt(i, entry);
} }
fn Glyph(index: u32, pos: GlyphPos) -> Glyph { fn add_glyphs_for_index(i: uint, data_for_glyphs: &[GlyphData]) {
Glyph { assert i < self.entry_buffer.len();
index : index, assert data_for_glyphs.len() > 0;
pos : copy pos,
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]; #[legacy_exports];
export FreeTypeNativeFont, with_test_native_font, create; export FreeTypeNativeFont, with_test_native_font, create;
use util::*;
use vec_as_buf = vec::as_imm_buf; use vec_as_buf = vec::as_imm_buf;
use ptr::{addr_of, null}; use ptr::{addr_of, null};
use cast::reinterpret_cast; use cast::reinterpret_cast;
@ -18,6 +19,14 @@ use freetype::bindgen::{
FT_Set_Char_Size 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 { struct FreeTypeNativeFont {
/// The font binary. This must stay valid for the lifetime of the font /// The font binary. This must stay valid for the lifetime of the font
buf: @~[u8], buf: @~[u8],
@ -44,13 +53,13 @@ impl FreeTypeNativeFont {
return if idx != 0 as FT_UInt { return if idx != 0 as FT_UInt {
Some(idx as GlyphIndex) Some(idx as GlyphIndex)
} else { } else {
#warn("Invalid codepoint: %?", codepoint); debug!("Invalid codepoint: %?", codepoint);
None None
}; };
} }
// FIXME: What unit is this returning? Let's have a custom type // 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(); assert self.face.is_not_null();
let res = FT_Load_Glyph(self.face, glyph as FT_UInt, 0); let res = FT_Load_Glyph(self.face, glyph as FT_UInt, 0);
if res.succeeded() { if res.succeeded() {
@ -59,13 +68,11 @@ impl FreeTypeNativeFont {
let slot: FT_GlyphSlot = reinterpret_cast(&void_glyph); let slot: FT_GlyphSlot = reinterpret_cast(&void_glyph);
assert slot.is_not_null(); assert slot.is_not_null();
let advance = (*slot).metrics.horiAdvance; let advance = (*slot).metrics.horiAdvance;
#debug("h_advance for %? is %?", glyph, advance); debug!("h_advance for %? is %?", glyph, advance);
// FIXME: Dividing by 64 converts to pixels, which return Some(fixed_to_float_ft(advance) as FractionalPixel);
// is not the unit we should be using
return Some((advance / 64) as int);
} }
} else { } else {
#warn("Unable to load glyph %?. reason: %?", glyph, res); debug!("Unable to load glyph %?. reason: %?", glyph, res);
return None; return None;
} }
} }

View file

@ -2,8 +2,9 @@ extern mod cocoa;
export QuartzNativeFont, with_test_native_font, create; export QuartzNativeFont, with_test_native_font, create;
use font::FontMetrics; use font::{FontMetrics, FractionalPixel};
use au = gfx::geometry;
use libc::size_t; use libc::size_t;
use ptr::null; use ptr::null;
use glyph::GlyphIndex; use glyph::GlyphIndex;
@ -130,8 +131,7 @@ impl QuartzNativeFont {
return Some(glyphs[0] as GlyphIndex); 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<FractionalPixel> {
fn glyph_h_advance(glyph: GlyphIndex) -> Option<int> {
use coretext::{CGGlyph, kCTFontDefaultOrientation}; use coretext::{CGGlyph, kCTFontDefaultOrientation};
use coretext::coretext::{CTFontGetAdvancesForGlyphs}; use coretext::coretext::{CTFontGetAdvancesForGlyphs};
@ -141,7 +141,7 @@ impl QuartzNativeFont {
CTFontGetAdvancesForGlyphs(self.ctfont, kCTFontDefaultOrientation, glyph_buf, null(), 1) CTFontGetAdvancesForGlyphs(self.ctfont, kCTFontDefaultOrientation, glyph_buf, null(), 1)
}; };
return Some(advance as int); return Some(advance as FractionalPixel);
} }
fn get_metrics() -> FontMetrics { fn get_metrics() -> FontMetrics {
@ -164,6 +164,7 @@ impl QuartzNativeFont {
em_ascent: CTFontGetAscent(ctfont) as float * convFactor, em_ascent: CTFontGetAscent(ctfont) as float * convFactor,
em_descent: CTFontGetDescent(ctfont) as float * convFactor, em_descent: CTFontGetDescent(ctfont) as float * convFactor,
em_height: em_ascent + em_descent, em_height: em_ascent + em_descent,
em_size: au::from_pt(21f),
max_advance: bounding_rect.size.width as float * convFactor, 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; use coretext::coretext::CTFontCreateWithGraphicsFont;
assert cgfont.is_not_null(); assert cgfont.is_not_null();
// TODO: use actual font size here!
CTFontCreateWithGraphicsFont(cgfont, 21f as CGFloat, null(), null()) CTFontCreateWithGraphicsFont(cgfont, 21f as CGFloat, null(), null())
} }

View file

@ -1,23 +1,26 @@
extern mod harfbuzz; extern mod harfbuzz;
export shape_text;
use au = gfx::geometry; 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::types::common::c99::int32_t;
use libc::{c_uint, c_int, c_void, c_char}; 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 ptr::{null, to_unsafe_ptr, offset};
use gfx::geometry::au; use std::arc;
use geom::point::Point2D; use text_run::TextRun;
use font_cache::FontCache; use util::*;
use cast::reinterpret_cast; use cast::reinterpret_cast;
use harfbuzz::{HB_MEMORY_MODE_READONLY, use harfbuzz::{HB_MEMORY_MODE_READONLY,
HB_DIRECTION_LTR}; 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_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, use harfbuzz::bindgen::{hb_blob_create, hb_blob_destroy,
hb_face_create, hb_face_destroy, hb_face_create, hb_face_destroy,
hb_font_create, hb_font_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_func,
hb_font_funcs_set_glyph_h_kerning_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 Calculate the layout metrics associated with a some given text
when rendered in a specific font. when rendered in a specific font.
"] */
fn shape_text(font: &Font, text: &str) -> ~[Glyph] unsafe { pub fn shape_textrun(font: &Font, run: &TextRun) {
#debug("shaping text '%s'", text); debug!("shaping text '%s'", run.text);
let face_blob = vec::as_imm_buf(*(*font).buf(), |buf, len| { // TODO: harfbuzz fonts and faces should be cached on the Font object.
hb_blob_create(reinterpret_cast(&buf), // 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, len as c_uint,
HB_MEMORY_MODE_READONLY, HB_MEMORY_MODE_READONLY,
null(), null(),
null()) null())
}); });
let hbface = hb_face_create(face_blob, 0 as c_uint); let hb_face: *hb_face_t = hb_face_create(face_blob, 0 as c_uint);
let hbfont = hb_font_create(hbface); let hb_font: *hb_font_t = hb_font_create(hb_face);
hb_font_set_ppem(hbfont, 10 as c_uint, 10 as c_uint); // TODO: set font size here, based on Font's size
hb_font_set_scale(hbfont, 10 as c_int, 10 as c_int); // 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_func(funcs, glyph_func, null(), null());
hb_font_funcs_set_glyph_h_advance_func(funcs, glyph_h_advance_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 // Using as_buf because it never does a copy - we don't need the trailing null
str::as_buf(text, |ctext, _l| { str::as_buf(run.text, |ctext: *u8, _l: uint| {
hb_buffer_add_utf8(buffer, ctext as *c_char, hb_buffer_add_utf8(hb_buffer,
text.len() as c_int, ctext as *c_char,
run.text.len() as c_int,
0 as c_uint, 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_buf_len = 0 as c_uint;
let info_ = hb_buffer_get_glyph_infos(buffer, to_unsafe_ptr(&info_len)); let info_buf = hb_buffer_get_glyph_infos(hb_buffer, to_unsafe_ptr(&info_buf_len));
assert info_.is_not_null(); assert info_buf.is_not_null();
let pos_len = 0 as c_uint; let pos_buf_len = 0 as c_uint;
let pos = hb_buffer_get_glyph_positions(buffer, to_unsafe_ptr(&pos_len)); let pos_buf = hb_buffer_get_glyph_positions(hb_buffer, to_unsafe_ptr(&pos_buf_len));
assert pos.is_not_null(); 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 data = GlyphData(codepoint, advance, offset, false, false, false);
let info_ = offset(info_, i); run.glyphs.add_glyph_for_index(i, &data);
let pos = offset(pos, i); } /* unsafe */ }
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);
glyphs += ~[Glyph(codepoint, pos)]; hb_buffer_destroy(hb_buffer);
}
hb_buffer_destroy(buffer);
hb_font_funcs_destroy(funcs); hb_font_funcs_destroy(funcs);
hb_font_destroy(hbfont); hb_font_destroy(hb_font);
hb_face_destroy(hbface); hb_face_destroy(hb_face);
hb_blob_destroy(face_blob); hb_blob_destroy(face_blob);
return glyphs;
} }
extern fn glyph_func(_font: *hb_font_t, 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, glyph: *mut hb_codepoint_t,
_user_data: *c_void) -> hb_bool_t unsafe { _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(); assert font.is_not_null();
return match (*font).glyph_index(unicode as char) { return match (*font).glyph_index(unicode as char) {
Some(g) => { Some(g) => { *glyph = g as hb_codepoint_t; true },
*glyph = g as hb_codepoint_t; None => false
true
}
None => {
false
}
} as hb_bool_t; } as hb_bool_t;
} }
@ -129,19 +153,11 @@ extern fn glyph_h_advance_func(_font: *hb_font_t,
font_data: *c_void, font_data: *c_void,
glyph: hb_codepoint_t, glyph: hb_codepoint_t,
_user_data: *c_void) -> hb_position_t unsafe { _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(); assert font.is_not_null();
let h_advance = (*font).glyph_h_advance(glyph as u32); let advance = (*font).glyph_h_advance(glyph as GlyphIndex);
#debug("h_advance for codepoint %? is %?", glyph, h_advance); float_to_fixed_hb(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)))
} }
fn should_get_glyph_indexes() { fn should_get_glyph_indexes() {

View file

@ -1,138 +1,137 @@
use arc = std::arc;
use arc::ARC;
use au = gfx::geometry; use au = gfx::geometry;
use font::Font;
use font_cache::FontCache;
use geom::point::Point2D; use geom::point::Point2D;
use geom::size::Size2D; use geom::size::Size2D;
use gfx::geometry::au; use gfx::geometry::au;
use glyph::GlyphStore;
use layout::context::LayoutContext;
use libc::{c_void}; use libc::{c_void};
use font_cache::FontCache; use servo_util::color;
use font::Font; use shaper::shape_textrun;
use glyph::Glyph; use std::arc;
use shaper::shape_text;
/// A single, unbroken line of text pub struct TextRun {
struct TextRun {
text: ~str, text: ~str,
priv glyphs: ~[Glyph], priv glyphs: GlyphStore,
priv size_: Size2D<au>,
priv min_break_width_: au,
} }
impl TextRun { impl TextRun {
/// The size of the entire TextRun pure fn glyphs(&self) -> &self/GlyphStore { &self.glyphs }
pure fn size() -> Size2D<au> { self.size_ }
pure fn min_break_width() -> au { self.min_break_width_ }
/// Split a run of text in two fn min_width_for_range(ctx: &LayoutContext, offset: uint, length: uint) -> au {
// FIXME: Should be storing a reference to the Font inside assert length > 0;
// of the TextRun, but I'm hitting cycle collector bugs assert offset < self.text.len();
fn split(font: &Font, h_offset: au) -> (TextRun, TextRun) { assert offset + length <= self.text.len();
assert h_offset >= self.min_break_width();
assert h_offset <= self.size_.width;
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 metrics = font.measure_text(&self, piece_offset, piece_len);
let mut candidate = copy curr_run; if metrics.advance > max_piece_width {
max_piece_width = metrics.advance;
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 second = str::slice(self.text, second_start, self.text.len()); return max_piece_width;
return (TextRun(font, first), TextRun(font, second));
} }
}
fn TextRun(font: &Font, +text: ~str) -> TextRun { fn iter_natural_lines_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool) {
let glyphs = shape_text(font, text); assert length > 0;
let size = glyph_run_size(glyphs); assert offset < self.text.len();
let min_break_width = calc_min_break_width(font, text); assert offset + length <= self.text.len();
TextRun { let mut clump_start = offset;
text: text, let mut clump_end = offset;
glyphs: shape_text(font, text), let mut in_clump = false;
size_: size,
min_break_width_: min_break_width
}
}
fn glyph_run_size(glyphs: &[Glyph]) -> Size2D<au> { // clump non-linebreaks of nonzero length
let height = au::from_px(20); for uint::range(offset, offset + length) |i| {
let pen_start_x = au::from_px(0); match (self.glyphs.char_is_newline(i), in_clump) {
let pen_start_y = height; (false, true) => { clump_end = i; }
let pen_start = Point2D(pen_start_x, pen_start_y); (false, false) => { in_clump = true; clump_start = i; clump_end = i; }
let pen_end = glyphs.foldl(pen_start, |cur, glyph| { (true, false) => { /* chomp whitespace */ }
Point2D(cur.x.add(&glyph.pos.offset.x).add(&glyph.pos.advance.x), (true, true) => {
cur.y.add(&glyph.pos.offset.y).add(&glyph.pos.advance.y)) in_clump = false;
}); // don't include the linebreak 'glyph'
return Size2D(pen_end.x, pen_end.y); // (we assume there's one GlyphEntry for a newline, and no actual glyphs)
} if !f(clump_start, clump_end - clump_start + 1) { break }
}
/// 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| { // flush any remaining chars as a line
let glyphs = shape_text(font, slice); if in_clump {
let size = glyph_run_size(glyphs); clump_end = offset + length - 1;
if size.width > max_piece_width { f(clump_start, clump_end - clump_start + 1);
max_piece_width = size.width
} }
} }
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 /// 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] #[test]
fn test_calc_min_break_width1() { fn test_calc_min_break_width1() {
let flib = FontCache(); 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"] pub fn fixed_to_float(before: int, f: i32) -> float {
fn true_type_tag(a: char, b: char, c: char, d: char) -> u32 { 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 (a << 24 | b << 16 | c << 8 | d) as u32
} }