mirror of
https://github.com/servo/servo.git
synced 2025-08-07 14:35:33 +01:00
Overhaul handling of glyphs, glyph shaping, text runs, and hacky line breaking.
This commit is contained in:
parent
a272a50635
commit
839ce9c867
15 changed files with 841 additions and 482 deletions
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 => {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue