From 839ce9c86710350f57d87f62bcb331ac4f38da16 Mon Sep 17 00:00:00 2001 From: "Brian J. Burg" Date: Mon, 8 Oct 2012 17:54:40 -0700 Subject: [PATCH] Overhaul handling of glyphs, glyph shaping, text runs, and hacky line breaking. --- src/servo/gfx/display_list.rs | 34 +- src/servo/gfx/geometry.rs | 16 +- src/servo/gfx/render_task.rs | 66 ++- src/servo/layout/block.rs | 6 +- src/servo/layout/box.rs | 76 +-- src/servo/layout/box_builder.rs | 13 +- src/servo/layout/inline.rs | 64 ++- src/servo/layout/text.rs | 47 +- src/servo/text/font.rs | 57 +- src/servo/text/glyph.rs | 515 ++++++++++++++---- src/servo/text/native_font/ft_native_font.rs | 21 +- .../text/native_font/quartz_native_font.rs | 10 +- src/servo/text/shaper.rs | 156 +++--- src/servo/text/text_run.rs | 221 ++++---- src/servo/text/util.rs | 21 +- 15 files changed, 841 insertions(+), 482 deletions(-) diff --git a/src/servo/gfx/display_list.rs b/src/servo/gfx/display_list.rs index 1702eb29d7f..895dd67672c 100644 --- a/src/servo/gfx/display_list.rs +++ b/src/servo/gfx/display_list.rs @@ -1,5 +1,5 @@ use azure::azure_hl::DrawTarget; -use gfx::render_task::{draw_solid_color, draw_image, draw_glyphs}; +use gfx::render_task::{draw_solid_color, draw_image, draw_text}; use gfx::geometry::*; use geom::rect::Rect; use image::base::Image; @@ -7,30 +7,24 @@ use render_task::RenderContext; use std::arc::{ARC, clone}; use dvec::DVec; -use text::glyph::Glyph; +use text::text_run::TextRun; pub use layout::display_list_builder::DisplayListBuilder; +// TODO: invert this so common data is nested inside each variant as first arg. struct DisplayItem { draw: ~fn((&DisplayItem), (&RenderContext)), bounds : Rect, // TODO: whose coordinate system should this use? data : DisplayItemData } -enum DisplayItemData { +pub enum DisplayItemData { SolidColorData(u8, u8, u8), - GlyphData(GlyphRun), + // TODO: need to provide spacing data for text run. + // (i.e, to support rendering of CSS 'word-spacing' and 'letter-spacing') + // TODO: don't copy text runs, ever. + TextData(~TextRun, uint, uint), ImageData(ARC<~image::base::Image>), - PaddingData(u8, u8, u8, u8) // This is a hack to make fonts work (?) -} - -/** -A run of glyphs in a single font. This is distinguished from any similar -structure used by layout in that this must be sendable, whereas the text -shaping data structures may end up unsendable. -*/ -pub struct GlyphRun { - glyphs: ~[Glyph] } fn draw_SolidColor(self: &DisplayItem, ctx: &RenderContext) { @@ -40,9 +34,9 @@ fn draw_SolidColor(self: &DisplayItem, ctx: &RenderContext) { } } -fn draw_Glyphs(self: &DisplayItem, ctx: &RenderContext) { +fn draw_Text(self: &DisplayItem, ctx: &RenderContext) { match self.data { - GlyphData(run) => draw_glyphs(ctx, self.bounds, &run), + TextData(~run, offset, len) => draw_text(ctx, self.bounds, &run, offset, len), _ => fail } } @@ -56,25 +50,23 @@ fn draw_Image(self: &DisplayItem, ctx: &RenderContext) { pub fn SolidColor(bounds: Rect, r: u8, g: u8, b: u8) -> DisplayItem { DisplayItem { - // TODO: this seems wrong. draw: |self, ctx| draw_SolidColor(self, ctx), bounds: bounds, data: SolidColorData(r, g, b) } } -pub fn Glyphs(bounds: Rect, run: GlyphRun) -> DisplayItem { +pub fn Text(bounds: Rect, run: ~TextRun, offset: uint, length: uint) -> DisplayItem { DisplayItem { - draw: |self, ctx| draw_Glyphs(self, ctx), + draw: |self, ctx| draw_Text(self, ctx), bounds: bounds, - data: GlyphData(run) + data: TextData(run, offset, length) } } // ARC should be cloned into ImageData, but Images are not sendable pub fn Image(bounds: Rect, image: ARC<~image::base::Image>) -> DisplayItem { DisplayItem { - // TODO: this seems wrong. draw: |self, ctx| draw_Image(self, ctx), bounds: bounds, data: ImageData(clone(&image)) diff --git a/src/servo/gfx/geometry.rs b/src/servo/gfx/geometry.rs index 63b4389c63b..1ce98a99c9f 100644 --- a/src/servo/gfx/geometry.rs +++ b/src/servo/gfx/geometry.rs @@ -39,20 +39,23 @@ pub fn box(x: A, y: A, w: A, h: A) -> Rect { Rect(Point2D(x, y), Size2D(w, h)) } -pub fn zero_rect() -> Rect { +pub pure fn zero_rect() -> Rect { let z = au(0); Rect(Point2D(z, z), Size2D(z, z)) } - -pub fn zero_point() -> Point2D { +pub pure fn zero_point() -> Point2D { Point2D(au(0), au(0)) } -pub fn zero_size() -> Size2D { +pub pure fn zero_size() -> Size2D { Size2D(au(0), au(0)) } +pub pure fn from_frac_px(f: float) -> au { + au((f * 60f) as i32) +} + pub pure fn from_px(i: int) -> au { from_int(i * 60) } @@ -60,3 +63,8 @@ pub pure fn from_px(i: int) -> au { pub pure fn to_px(au: au) -> int { (*au / 60) as int } + +// assumes 72 points per inch, and 96 px per inch +pub pure fn from_pt(f: float) -> au { + from_int((f * 96f / 72f) as int) +} \ No newline at end of file diff --git a/src/servo/gfx/render_task.rs b/src/servo/gfx/render_task.rs index b6d385a9e85..bbf703a9b4c 100644 --- a/src/servo/gfx/render_task.rs +++ b/src/servo/gfx/render_task.rs @@ -1,28 +1,31 @@ use mod azure::azure_hl; -use au = geometry; + +use au = gfx::geometry; use au::au; -use platform::osmain; -use comm::*; -use image::base::Image; -use dl = display_list; -use libc::size_t; -use text::font::Font; -use display_list::GlyphRun; -use geom::size::Size2D; -use geom::rect::Rect; -use geom::point::Point2D; -use azure::{AzDrawOptions, AzFloat, AzGlyph, AzGlyphBuffer}; use azure::bindgen::AzDrawTargetFillGlyphs; +use azure::cairo::{cairo_font_face_t, cairo_scaled_font_t}; +use azure::{AzDrawOptions, AzFloat, AzGlyph, AzGlyphBuffer}; use azure_hl::{AsAzureRect, B8G8R8A8, Color, ColorPattern, DrawOptions, DrawSurfaceOptions}; use azure_hl::{DrawTarget, Linear}; +use comm::*; +use compositor::Compositor; +use core::dvec::DVec; +use dl = display_list; +use geom::point::Point2D; +use geom::rect::Rect; +use geom::size::Size2D; +use image::base::Image; +use libc::size_t; +use pipes::{Port, Chan}; +use platform::osmain; use ptr::to_unsafe_ptr; use std::arc::ARC; -use azure::cairo::{cairo_font_face_t, cairo_scaled_font_t}; use std::cell::Cell; -use compositor::Compositor; -use servo_text::font_cache::FontCache; +use text::text_run::TextRun; +use text::font::Font; +use text::font_cache::FontCache; + -use pipes::{Port, Chan}; pub type Renderer = comm::Chan; @@ -133,7 +136,7 @@ pub fn draw_image(ctx: &RenderContext, bounds: Rect, image: ARC<~Image>) { draw_options); } -pub fn draw_glyphs(ctx: &RenderContext, bounds: Rect, text_run: &GlyphRun) { +pub fn draw_text(ctx: &RenderContext, bounds: Rect, run: &TextRun, offset: uint, length: uint) { use ptr::{null}; use vec::raw::to_ptr; use libc::types::common::c99::{uint16_t, uint32_t}; @@ -147,7 +150,7 @@ pub fn draw_glyphs(ctx: &RenderContext, bounds: Rect, text_run: &GlyphRun) { AzReleaseColorPattern}; use azure::cairo::bindgen::cairo_scaled_font_destroy; - // FIXME: font should be accessible from GlyphRun + // FIXME: font should be accessible from TextRun let font = ctx.font_cache.get_test_font(); let nfont: AzNativeFont = { @@ -175,22 +178,29 @@ pub fn draw_glyphs(ctx: &RenderContext, bounds: Rect, text_run: &GlyphRun) { }; let mut origin = Point2D(bounds.origin.x, bounds.origin.y.add(&bounds.size.height)); - let azglyphs = text_run.glyphs.map(|glyph| { + let azglyphs = DVec(); + azglyphs.reserve(length); + + do run.glyphs.iter_glyphs_for_range(offset, length) |_i, glyph| { + let glyph_advance = glyph.advance(); + let glyph_offset = glyph.offset().get_default(au::zero_point()); + let azglyph: AzGlyph = { - mIndex: glyph.index as uint32_t, + mIndex: glyph.index() as uint32_t, mPosition: { - x: au::to_px(origin.x.add(&glyph.pos.offset.x)) as AzFloat, - y: au::to_px(origin.y.add(&glyph.pos.offset.y)) as AzFloat + x: au::to_px(origin.x + glyph_offset.x) as AzFloat, + y: au::to_px(origin.y + glyph_offset.y) as AzFloat } }; - origin = Point2D(origin.x.add(&glyph.pos.advance.x), - origin.y.add(&glyph.pos.advance.y)); - azglyph - }); + origin = Point2D(origin.x + glyph_advance, origin.y); + azglyphs.push(move azglyph) + }; + let azglyph_buf_len = azglyphs.len(); + let azglyph_buf = dvec::unwrap(move azglyphs); let glyphbuf: AzGlyphBuffer = unsafe {{ - mGlyphs: to_ptr(azglyphs), - mNumGlyphs: azglyphs.len() as uint32_t + mGlyphs: to_ptr(azglyph_buf), + mNumGlyphs: azglyph_buf_len as uint32_t }}; // TODO: this call needs to move into azure_hl.rs diff --git a/src/servo/layout/block.rs b/src/servo/layout/block.rs index 8a097466e73..c5a77be9a2c 100644 --- a/src/servo/layout/block.rs +++ b/src/servo/layout/block.rs @@ -66,7 +66,7 @@ impl FlowContext : BlockLayout { /* TODO: floats */ /* TODO: absolute contexts */ /* TODO: inline-blocks */ - fn bubble_widths_block(_ctx: &LayoutContext) { + fn bubble_widths_block(ctx: &LayoutContext) { assert self.starts_block_flow(); let mut min_width = au(0); @@ -83,8 +83,8 @@ impl FlowContext : BlockLayout { /* if not an anonymous block context, add in block box's widths. these widths will not include child elements, just padding etc. */ do self.with_block_box |box| { - min_width = min_width.add(&box.get_min_width()); - pref_width = pref_width.add(&box.get_pref_width()); + min_width = min_width.add(&box.get_min_width(ctx)); + pref_width = pref_width.add(&box.get_pref_width(ctx)); } self.d().min_width = min_width; diff --git a/src/servo/layout/box.rs b/src/servo/layout/box.rs index 781e913f194..9f497ac64cf 100644 --- a/src/servo/layout/box.rs +++ b/src/servo/layout/box.rs @@ -93,7 +93,8 @@ enum RenderBoxType { pub enum RenderBox { GenericBox(RenderBoxData), ImageBox(RenderBoxData, ImageHolder), - TextBox(RenderBoxData, TextBoxData) + TextBox(RenderBoxData, TextBoxData), + UnscannedTextBox(RenderBoxData, ~str) } impl RenderBox { @@ -101,7 +102,8 @@ impl RenderBox { match *self { GenericBox(ref d) => d, ImageBox(ref d, _) => d, - TextBox(ref d, _) => d + TextBox(ref d, _) => d, + UnscannedTextBox(ref d, _) => d, } } } @@ -130,7 +132,7 @@ impl RenderBox { * may cause glyphs to be allocated. For now, it's impure because of * holder.get_image() */ - fn get_min_width() -> au { + fn get_min_width(ctx: &LayoutContext) -> au { match self { // TODO: this should account for min/pref widths of the // box element in isolation. That includes @@ -141,13 +143,12 @@ impl RenderBox { // TODO: consult CSS 'width', margin, border. // TODO: If image isn't available, consult 'width'. ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width), - TextBox(_,d) => d.runs.foldl(au(0), |sum, run| { - au::max(*sum, run.min_break_width()) - }) + TextBox(_,d) => d.run.min_width_for_range(ctx, d.offset, d.length), + UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here." } } - fn get_pref_width() -> au { + fn get_pref_width(_ctx: &LayoutContext) -> au { match self { // TODO: this should account for min/pref widths of the // box element in isolation. That includes @@ -156,12 +157,30 @@ impl RenderBox { // that of its children to arrive at the context width. GenericBox(*) => au(0), ImageBox(_,i) => au::from_px(i.get_size().get_default(Size2D(0,0)).width), - // TODO: account for line breaks, etc. The run should know - // how to compute its own min and pref widths, and should - // probably cache them. - TextBox(_,d) => d.runs.foldl(au(0), |sum, run| { - au::max(*sum, run.size().width) - }) + + // a text box cannot span lines, so assume that this is an unsplit text box. + + // TODO: If text boxes have been split to wrap lines, then + // they could report a smaller pref width during incremental reflow. + // maybe text boxes should report nothing, and the parent flow could + // factor in min/pref widths of any text runs that it owns. + TextBox(_,d) => { + let mut max_line_width: au = au(0); + do d.run.iter_natural_lines_for_range(d.offset, d.length) |line_offset, line_len| { + let mut line_width: au = au(0); + do d.run.glyphs.iter_glyphs_for_range(line_offset, line_len) |_char_i, glyph| { + line_width += glyph.advance() + }; + + if max_line_width < line_width { + max_line_width = line_width; + }; + true + } + + max_line_width + }, + UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here." } } @@ -212,7 +231,8 @@ impl RenderBox { }, TextBox(*) => { copy self.d().position - } + }, + UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here." } } @@ -251,25 +271,9 @@ impl RenderBox { copy self.d().position.size); match self { + UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here.", TextBox(_,d) => { - let mut runs = d.runs; - // TODO: don't paint background for text boxes - list.push(~dl::SolidColor(bounds, 255u8, 255u8, 255u8)); - - let mut bounds = bounds; - for uint::range(0, runs.len()) |i| { - bounds.size.height = runs[i].size().height; - let glyph_run = make_glyph_run(&runs[i]); - list.push(~dl::Glyphs(bounds, glyph_run)); - bounds.origin.y = bounds.origin.y.add(&bounds.size.height); - } - return; - - pure fn make_glyph_run(text_run: &TextRun) -> dl::GlyphRun { - dl::GlyphRun { - glyphs: copy text_run.glyphs - } - } + list.push(~dl::Text(bounds, ~(copy *d.run), d.offset, d.length)) }, // TODO: items for background, border, outline GenericBox(*) => { }, @@ -348,12 +352,8 @@ impl RenderBox : BoxedDebugMethods { let repr = match self { @GenericBox(*) => ~"GenericBox", @ImageBox(*) => ~"ImageBox", - @TextBox(_,d) => { - let mut s = d.runs.foldl(~"TextBox(runs=", |s, run| { - fmt!("%s \"%s\"", *s, run.text) - }); - s += ~")"; s - } + @TextBox(_,d) => fmt!("TextBox(text=%s)", str::substr(d.run.text, d.offset, d.length)), + @UnscannedTextBox(_,s) => fmt!("UnscannedTextBox(%s)", s) }; fmt!("box b%?: %?", self.d().id, repr) diff --git a/src/servo/layout/box_builder.rs b/src/servo/layout/box_builder.rs index 5e901ae5a11..01983435570 100644 --- a/src/servo/layout/box_builder.rs +++ b/src/servo/layout/box_builder.rs @@ -13,10 +13,7 @@ use layout::context::LayoutContext; use layout::flow::*; use layout::inline::InlineFlowData; use layout::root::RootFlowData; -use layout::text::TextBoxData; use option::is_none; -use servo_text::font_cache::FontCache; -use servo_text::text_run::TextRun; use util::tree; export LayoutTreeBuilder; @@ -242,16 +239,10 @@ impl LayoutTreeBuilder { } - fn make_text_box(layout_ctx: &LayoutContext, node: Node, ctx: @FlowContext) -> @RenderBox { + fn make_text_box(_layout_ctx: &LayoutContext, node: Node, ctx: @FlowContext) -> @RenderBox { do node.read |n| { match n.kind { - ~Text(string) => { - // TODO: clean this up. Fonts should not be created here. - let font = layout_ctx.font_cache.get_test_font(); - let run = TextRun(font, string); - @TextBox(RenderBoxData(node, ctx, self.next_box_id()), - TextBoxData(copy string, ~[move run])) - }, + ~Text(string) => @UnscannedTextBox(RenderBoxData(node, ctx, self.next_box_id()), copy string), _ => fail ~"WAT error: why couldn't we make a text box?" } } diff --git a/src/servo/layout/inline.rs b/src/servo/layout/inline.rs index cf37322e12b..591cf80998f 100644 --- a/src/servo/layout/inline.rs +++ b/src/servo/layout/inline.rs @@ -8,10 +8,13 @@ use geom::point::Point2D; use geom::rect::Rect; use geom::size::Size2D; use gfx::geometry::au; -use layout::box::{RenderBox, RenderBoxTree, ImageBox, TextBox, GenericBox}; -use layout::flow::{FlowContext, InlineFlow}; +use layout::box::{RenderBox, RenderBoxTree, ImageBox, TextBox, GenericBox, UnscannedTextBox}; use layout::context::LayoutContext; +use layout::flow::{FlowContext, InlineFlow}; +use layout::text::TextBoxData; use num::Num; +use servo_text::text_run::TextRun; +use std::arc; use util::tree; /* @@ -40,10 +43,24 @@ hard to try out that alternative. type BoxRange = {start: u8, len: u8}; +// TODO: flesh out into TextRunScanner +fn build_runs_for_flow(ctx: &LayoutContext, dummy_boxes: &DVec<@RenderBox>) { + for uint::range(0, dummy_boxes.len()) |i| { + match *dummy_boxes[i] { + UnscannedTextBox(d, text) => { + let run = TextRun(&*ctx.font_cache.get_test_font(), text); + let box_guts = TextBoxData(@run, 0, text.len()); + dummy_boxes.set_elt(i, @TextBox(d, box_guts)); + }, + _ => {} + } + } +} + struct InlineFlowData { - // A flat list of all inline render boxes. Several boxes may + // A vec of all inline render boxes. Several boxes may // correspond to one Node/Element. - boxes: DList<@RenderBox>, + boxes: DVec<@RenderBox>, // vec of ranges into boxes that represents line positions. // these ranges are disjoint, and are the result of inline layout. lines: DVec, @@ -55,7 +72,7 @@ struct InlineFlowData { fn InlineFlowData() -> InlineFlowData { InlineFlowData { - boxes: DList(), + boxes: DVec(), lines: DVec(), elems: DVec() } @@ -73,15 +90,18 @@ trait InlineLayout { impl FlowContext : InlineLayout { pure fn starts_inline_flow() -> bool { match self { InlineFlow(*) => true, _ => false } } - fn bubble_widths_inline(_ctx: &LayoutContext) { + fn bubble_widths_inline(ctx: &LayoutContext) { assert self.starts_inline_flow(); + // TODO: this is a hack + build_runs_for_flow(ctx, &self.inline().boxes); + let mut min_width = au(0); let mut pref_width = au(0); for self.inline().boxes.each |box| { - min_width = au::max(min_width, box.get_min_width()); - pref_width = au::max(pref_width, box.get_pref_width()); + min_width = au::max(min_width, box.get_min_width(ctx)); + pref_width = au::max(pref_width, box.get_pref_width(ctx)); } self.d().min_width = min_width; @@ -101,6 +121,9 @@ impl FlowContext : InlineLayout { //let mut cur_x = au(0); let mut cur_y = au(0); + // TODO: remove test font uses + let test_font = ctx.font_cache.get_test_font(); + for self.inline().boxes.each |box| { /* TODO: actually do inline flow. - Create a working linebox, and successively put boxes @@ -111,25 +134,26 @@ impl FlowContext : InlineLayout { - Save the dvec of this context's lineboxes. */ - /* hack: until text box splitting is hoisted into this - function, force "reflow" on TextBoxes. */ - match *box { - @TextBox(*) => box.reflow_text(ctx), - _ => {} - } - box.d().position.size.width = match *box { @ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).width), - @TextBox(_,d) => d.runs[0].size().width, + @TextBox(_,d) => { + // TODO: measure twice, cut once doesn't apply to text. Shouldn't need + // to measure text again here (should be inside TextBox.split) + let metrics = test_font.measure_text(d.run, d.offset, d.length); + metrics.advance + }, // TODO: this should be set to the extents of its children - @GenericBox(*) => au(0) + @GenericBox(*) => au(0), + _ => fail fmt!("Tried to assign width to unknown Box variant: %?", box) }; box.d().position.size.height = match *box { @ImageBox(_,img) => au::from_px(img.get_size().get_default(Size2D(0,0)).height), - @TextBox(_,d) => d.runs[0].size().height, + // TODO: we should use the bounding box of the actual text, i think? + @TextBox(*) => test_font.metrics.em_size, // TODO: this should be set to the extents of its children - @GenericBox(*) => au(0) + @GenericBox(*) => au(0), + _ => fail fmt!("Tried to assign width to unknown Box variant: %?", box) }; box.d().position.origin = Point2D(au(0), cur_y); @@ -161,7 +185,7 @@ impl FlowContext : InlineLayout { // TODO: once we form line boxes and have their cached bounds, we can be // smarter and not recurse on a line if nothing in it can intersect dirty - debug!("building display list for %u", self.inline().boxes.len()); + debug!("building display list for %u inline boxes", self.inline().boxes.len()); for self.inline().boxes.each |box| { box.build_display_list(builder, dirty, offset, list) } diff --git a/src/servo/layout/text.rs b/src/servo/layout/text.rs index d9abc3e7531..6b6f49be3a6 100644 --- a/src/servo/layout/text.rs +++ b/src/servo/layout/text.rs @@ -1,30 +1,28 @@ /** Text layout. */ use au = gfx::geometry; +use au::au; use geom::size::Size2D; -use gfx::geometry::au; use servo_text::text_run::TextRun; use servo_text::font_cache::FontCache; use layout::box::{TextBox, RenderBox}; use layout::context::LayoutContext; -struct TextBoxData { - text: ~str, - mut runs: ~[TextRun] +pub struct TextBoxData { + run: @TextRun, + offset: uint, + length: uint } -fn TextBoxData(text: ~str, runs: ~[TextRun]) -> TextBoxData { +pub fn TextBoxData(run: @TextRun, offset: uint, length: uint) -> TextBoxData { TextBoxData { - text: text, - runs: runs + run: run, + offset: offset, + length: length } } -trait TextLayout { - fn reflow_text(ctx: &LayoutContext); -} - -/** The main reflow routine for text layout. */ +/* The main reflow routine for text layout. impl @RenderBox : TextLayout { fn reflow_text(ctx: &LayoutContext) { let d = match self { @@ -32,6 +30,7 @@ impl @RenderBox : TextLayout { _ => { fail ~"expected text box in reflow_text!" } }; + // TODO: get font from textrun's TextStyle let font = ctx.font_cache.get_test_font(); // Do line breaking. @@ -76,26 +75,4 @@ impl @RenderBox : TextLayout { self.d().position.size = Size2D(max_width, total_height); d.runs = move dvec::unwrap(lines); } -} - -/* TODO: new unit tests for TextBox splitting, etc -fn should_calculate_the_size_of_the_text_box() { - #[test]; - #[ignore(cfg(target_os = "macos"))]; - - use au = gfx::geometry; - use dom::rcu::{Scope}; - use dom::base::{Text, NodeScope}; - use util::tree; - use layout::box_builder::LayoutTreeBuilder; - - let s = Scope(); - let n = s.new_node(Text(~"firecracker")); - let builder = LayoutTreeBuilder(); - let b = builder.construct_trees(n).get(); - - b.reflow_text(); - let expected = Size2D(au::from_px(84), au::from_px(20)); - assert b.data.position.size == expected; -} -*/ \ No newline at end of file +}*/ diff --git a/src/servo/text/font.rs b/src/servo/text/font.rs index d9806a57126..a073dc7f0f1 100644 --- a/src/servo/text/font.rs +++ b/src/servo/text/font.rs @@ -1,11 +1,16 @@ pub use font_cache::FontCache; -export Font, FontMetrics, test_font_bin, create_test_font; +use au = gfx::geometry; +use au::au; use glyph::GlyphIndex; -use vec_to_ptr = vec::raw::to_ptr; use libc::{ c_int, c_double, c_ulong }; -use ptr::{ null, addr_of }; use native_font::NativeFont; +use ptr::{null, addr_of}; +use text::text_run::TextRun; +use vec_to_ptr = vec::raw::to_ptr; + +// Used to abstract over the shaper's choice of fixed int representation. +type FractionalPixel = float; /** A font handle. Layout can use this to calculate glyph metrics @@ -16,11 +21,38 @@ struct Font { lib: @FontCache, fontbuf: @~[u8], native_font: NativeFont, - metrics: FontMetrics + metrics: FontMetrics, } -impl Font { - fn buf() -> @~[u8] { +struct RunMetrics { + advance: au, +} + +// Public API +pub trait FontMethods { + fn measure_text(run: &TextRun, offset: uint, length: uint) -> RunMetrics; + + fn buf(&self) -> @~[u8]; + // these are used to get glyphs and advances in the case that the + // shaper can't figure it out. + fn glyph_index(char) -> Option; + fn glyph_h_advance(GlyphIndex) -> FractionalPixel; +} + +pub impl Font : FontMethods { + fn measure_text(run: &TextRun, offset: uint, length: uint) -> RunMetrics { + // TODO: alter advance direction for RTL + // TODO: calculate actual bounding box as part of RunMetrics + // TODO: using inter-char and inter-word spacing settings when measuring text + let mut advance = au(0); + do run.glyphs.iter_glyphs_for_range(offset, length) |_i, glyph| { + advance += glyph.advance() + } + + RunMetrics { advance: advance } + } + + fn buf(&self) -> @~[u8] { self.fontbuf } @@ -28,14 +60,16 @@ impl Font { self.native_font.glyph_index(codepoint) } - fn glyph_h_advance(glyph: GlyphIndex) -> int { + fn glyph_h_advance(glyph: GlyphIndex) -> FractionalPixel { match self.native_font.glyph_h_advance(glyph) { Some(adv) => adv, - None => /* FIXME: Need fallback strategy */ 10 + None => /* FIXME: Need fallback strategy */ 10f as FractionalPixel } } } +// TODO: font should compute its own metrics using native_font. +// TODO: who should own fontbuf? fn Font(lib: @FontCache, fontbuf: @~[u8], +native_font: NativeFont, +metrics: FontMetrics) -> Font { Font { lib: lib, @@ -45,12 +79,19 @@ fn Font(lib: @FontCache, fontbuf: @~[u8], +native_font: NativeFont, +metrics: Fo } } +// TODO: what are the units of these metrics? CTFont metrics calls are +// font units for a specific point size, and these are normalized by +// font units-per-em. Are there some cases where these metrics depend +// on font size? If so, need to be careful about using same sizes on +// font creation, metrics gathering, and drawing. struct FontMetrics { underline_size: float, underline_offset: float, leading: float, x_height: float, + // how many appunits an em is equivalent to (based on point-to-au) + em_size: au, em_height: float, em_ascent: float, em_descent: float, diff --git a/src/servo/text/glyph.rs b/src/servo/text/glyph.rs index 792c248712c..3691fce2f19 100644 --- a/src/servo/text/glyph.rs +++ b/src/servo/text/glyph.rs @@ -2,22 +2,57 @@ use au = gfx::geometry; use au::au; use core::cmp::{Ord, Eq}; use core::dvec::DVec; +use core::u16; use geom::point::Point2D; use std::sort; use servo_util::vec::*; +use num::from_int; -export GlyphIndex, GlyphPos, Glyph; - -struct CompressedGlyph { - mut value : u32 +// GlyphEntry is a port of Gecko's CompressedGlyph scheme for storing +// glyph data compactly. +// +// In the common case (reasonable glyph advances, no offsets from the +// font em-box, and one glyph per character), we pack glyph advance, +// glyph id, and some flags into a single u32. +// +// In the uncommon case (multiple glyphs per unicode character, large +// glyph index/advance, or glyph offsets), we pack the glyph count +// into GlyphEntry, and store the other glyph information in +// DetailedGlyphStore. +struct GlyphEntry { + value : u32 } +pure fn GlyphEntry(value: u32) -> GlyphEntry { GlyphEntry { value: value } } + /// The index of a particular glyph within a font type GlyphIndex = u32; +// TODO: unify with bit flags? +enum BreakType { + BreakTypeNone, + BreakTypeNormal, + BreakTypeHyphen +} + const BREAK_TYPE_NONE : u8 = 0x0u8; const BREAK_TYPE_NORMAL : u8 = 0x1u8; -const BREAK_TYPE_HYPEN : u8 = 0x2u8; +const BREAK_TYPE_HYPHEN : u8 = 0x2u8; + +pure fn break_flag_to_enum(flag: u8) -> BreakType { + if (flag & BREAK_TYPE_NONE) as bool { return BreakTypeNone; } + if (flag & BREAK_TYPE_NORMAL) as bool { return BreakTypeNormal; } + if (flag & BREAK_TYPE_HYPHEN) as bool { return BreakTypeHyphen; } + fail ~"Unknown break setting" +} + +pure fn break_enum_to_flag(e: BreakType) -> u8 { + match e { + BreakTypeNone => BREAK_TYPE_NONE, + BreakTypeNormal => BREAK_TYPE_NORMAL, + BreakTypeHyphen => BREAK_TYPE_HYPHEN, + } +} // TODO: make this more type-safe. @@ -35,7 +70,7 @@ const GLYPH_ID_MASK : u32 = 0x0000FFFFu32; // Non-simple glyphs (more than one glyph per char; missing glyph, // newline, tab, large advance, or nonzero x/y offsets) may have one // or more detailed glyphs associated with them. They are stored in a -// side array so that there is a 1:1 mapping of CompressedGlyph to +// side array so that there is a 1:1 mapping of GlyphEntry to // unicode char. // The number of detailed glyphs for this char. If the char couldn't @@ -65,24 +100,28 @@ pure fn is_simple_advance(advance: au) -> bool { type DetailedGlyphCount = u16; -enum GlyphStoreResult { - Simple(T), - Detailed(u32) +pure fn InitialGlyphEntry() -> GlyphEntry { + GlyphEntry { value: 0 } } -fn SimpleGlyph(index: GlyphIndex, advance: au) -> CompressedGlyph { +// Creates a GlyphEntry for the common case +pure fn SimpleGlyphEntry(index: GlyphIndex, advance: au) -> GlyphEntry { assert is_simple_glyph_id(index); assert is_simple_advance(advance); let index_mask = index as u32; let advance_mask = (*advance as u32) << GLYPH_ADVANCE_SHIFT; - CompressedGlyph { + GlyphEntry { value: index_mask | advance_mask | FLAG_IS_SIMPLE_GLYPH } } -fn ComplexGlyph(startsCluster: bool, startsLigature: bool, glyphCount: u16) -> CompressedGlyph { +// Create a GlyphEntry for uncommon case; should be accompanied by +// initialization of the actual DetailedGlyph data in DetailedGlyphStore +pure fn ComplexGlyphEntry(startsCluster: bool, startsLigature: bool, glyphCount: uint) -> GlyphEntry { + assert glyphCount <= u16::max_value as uint; + let mut val = FLAG_NOT_MISSING; if !startsCluster { @@ -93,42 +132,47 @@ fn ComplexGlyph(startsCluster: bool, startsLigature: bool, glyphCount: u16) -> C } val |= (glyphCount as u32) << GLYPH_COUNT_SHIFT; - CompressedGlyph { + GlyphEntry { value: val } } -fn MissingGlyphs(glyphCount: u16) -> CompressedGlyph { - CompressedGlyph { +// Create a GlyphEntry for the case where glyphs couldn't be found +// for the specified character. +pure fn MissingGlyphsEntry(glyphCount: uint) -> GlyphEntry { + assert glyphCount <= u16::max_value as uint; + + GlyphEntry { value: (glyphCount as u32) << GLYPH_COUNT_SHIFT } } -impl CompressedGlyph { - pure fn advance() -> GlyphStoreResult { - match self.is_simple() { - true => Simple(num::from_int(((self.value & GLYPH_ADVANCE_MASK) >> GLYPH_ADVANCE_SHIFT) as int)), - false => Detailed(self.glyph_count()) - } +// Getters and setters for GlyphEntry. Setter methods are functional, +// because GlyphEntry is immutable and only a u32 in size. +impl GlyphEntry { + // getter methods + pure fn advance() -> au { + assert self.is_simple(); + from_int(((self.value & GLYPH_ADVANCE_MASK) >> GLYPH_ADVANCE_SHIFT) as int) } - pure fn glyph() -> GlyphStoreResult { - match self.is_simple() { - true => Simple(self.value & GLYPH_ID_MASK), - false => Detailed(self.glyph_count()) - } + pure fn index() -> GlyphIndex { + assert self.is_simple(); + self.value & GLYPH_ID_MASK } - pure fn offset() -> GlyphStoreResult> { - match self.is_simple() { - true => Simple(Point2D(au(0), au(0))), - false => Detailed(self.glyph_count()) - } + pure fn offset() -> Point2D { + assert self.is_simple(); + Point2D(au(0), au(0)) } - // getter methods + pure fn is_ligature_start() -> bool { + self.has_flag(!FLAG_NOT_LIGATURE_GROUP_START) + } - // TODO: some getters are still missing; add them as needed. + pure fn is_cluster_start() -> bool { + self.has_flag(!FLAG_NOT_CLUSTER_START) + } // True if original char was normal (U+0020) space. Other chars may // map to space glyph, but this does not account for them. @@ -144,46 +188,47 @@ impl CompressedGlyph { !self.is_simple() && self.has_flag(FLAG_CHAR_IS_NEWLINE) } - // TODO: make typesafe break enum - pure fn can_break_before() -> u8 { - ((self.value & FLAG_CAN_BREAK_MASK) >> FLAG_CAN_BREAK_SHIFT) as u8 + pure fn can_break_before() -> BreakType { + let flag = ((self.value & FLAG_CAN_BREAK_MASK) >> FLAG_CAN_BREAK_SHIFT) as u8; + break_flag_to_enum(flag) } // setter methods - - fn set_is_space() { - self.value |= FLAG_CHAR_IS_SPACE; + pure fn set_char_is_space() -> GlyphEntry { + GlyphEntry(self.value | FLAG_CHAR_IS_SPACE) } - fn set_is_tab() { + pure fn set_char_is_tab() -> GlyphEntry { assert !self.is_simple(); - self.value |= FLAG_CHAR_IS_TAB; + GlyphEntry(self.value | FLAG_CHAR_IS_TAB) } - fn set_is_newline() { + pure fn set_char_is_newline() -> GlyphEntry { assert !self.is_simple(); - self.value |= FLAG_CHAR_IS_NEWLINE; + GlyphEntry(self.value | FLAG_CHAR_IS_NEWLINE) } - // returns whether the setting had changed. - fn set_can_break_before(flags: u8) -> bool { - assert flags <= 0x2; - let mask = (flags as u32) << FLAG_CAN_BREAK_SHIFT; + // returns a glyph entry only if the setting had changed. + pure fn set_can_break_before(e: BreakType) -> Option { + let flag = break_enum_to_flag(e); + let mask = (flag as u32) << FLAG_CAN_BREAK_SHIFT; let toggle = mask ^ (self.value & FLAG_CAN_BREAK_MASK); - self.value ^= toggle; - toggle as bool + match (toggle as bool) { + true => Some(GlyphEntry(self.value ^ toggle)), + false => None + } } // helper methods - /*priv*/ pure fn glyph_count() -> u32 { + /*priv*/ pure fn glyph_count() -> u16 { assert !self.is_simple(); - (self.value & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT as u32 + ((self.value & GLYPH_COUNT_MASK) >> GLYPH_COUNT_SHIFT) as u16 } - /*priv*/ pure fn is_simple() -> bool { - (self.value & FLAG_IS_SIMPLE_GLYPH) == self.value + pure fn is_simple() -> bool { + self.has_flag(FLAG_IS_SIMPLE_GLYPH) } /*priv*/ pure fn has_flag(flag: u32) -> bool { @@ -191,93 +236,127 @@ impl CompressedGlyph { } } +// Stores data for a detailed glyph, in the case that several glyphs +// correspond to one character, or the glyph's data couldn't be packed. struct DetailedGlyph { - // The GlyphIndex, or the unicode codepoint if glyph is missing. - index: u32, + index: GlyphIndex, // glyph's advance, in the text's direction (RTL or RTL) advance: au, // glyph's offset from the font's em-box (from top-left) offset: Point2D } -// cg = CompressedGlyph, -// dg = DetailedGlyph + +fn DetailedGlyph(index: GlyphIndex, + advance: au, offset: Point2D) -> DetailedGlyph { + DetailedGlyph { + index: index, + advance: advance, + offset: offset + } +} struct DetailedGlyphRecord { - // source character/CompressedGlyph offset in the TextRun - cg_offset: u32, + // source string offset/GlyphEntry offset in the TextRun + entry_offset: uint, // offset into the detailed glyphs buffer - dg_offset: uint + detail_offset: uint } impl DetailedGlyphRecord : Ord { - pure fn lt(other: &DetailedGlyphRecord) -> bool { self.cg_offset < other.cg_offset } - pure fn le(other: &DetailedGlyphRecord) -> bool { self.cg_offset <= other.cg_offset } - pure fn ge(other: &DetailedGlyphRecord) -> bool { self.cg_offset >= other.cg_offset } - pure fn gt(other: &DetailedGlyphRecord) -> bool { self.cg_offset > other.cg_offset } + pure fn lt(other: &DetailedGlyphRecord) -> bool { self.entry_offset < other.entry_offset } + pure fn le(other: &DetailedGlyphRecord) -> bool { self.entry_offset <= other.entry_offset } + pure fn ge(other: &DetailedGlyphRecord) -> bool { self.entry_offset >= other.entry_offset } + pure fn gt(other: &DetailedGlyphRecord) -> bool { self.entry_offset > other.entry_offset } } impl DetailedGlyphRecord : Eq { - pure fn eq(other : &DetailedGlyphRecord) -> bool { self.cg_offset == other.cg_offset } - pure fn ne(other : &DetailedGlyphRecord) -> bool { self.cg_offset != other.cg_offset } + pure fn eq(other : &DetailedGlyphRecord) -> bool { self.entry_offset == other.entry_offset } + pure fn ne(other : &DetailedGlyphRecord) -> bool { self.entry_offset != other.entry_offset } } -// Manages the lookup table for detailed glyphs. +// Manages the lookup table for detailed glyphs. Sorting is deferred +// until a lookup is actually performed; this matches the expected +// usage pattern of setting/appending all the detailed glyphs, and +// then querying without setting. struct DetailedGlyphStore { - dg_buffer: DVec, - dg_lookup: DVec, + detail_buffer: DVec, + detail_lookup: DVec, mut lookup_is_sorted: bool, } fn DetailedGlyphStore() -> DetailedGlyphStore { DetailedGlyphStore { - dg_buffer: DVec(), - dg_lookup: DVec(), + detail_buffer: DVec(), + detail_lookup: DVec(), lookup_is_sorted: false } } impl DetailedGlyphStore { - fn add_glyphs_for_cg(cg_offset: u32, glyphs: &[DetailedGlyph]) { + fn add_detailed_glyphs_for_entry(entry_offset: uint, glyphs: &[DetailedGlyph]) { let entry = DetailedGlyphRecord { - cg_offset: cg_offset, - dg_offset: self.dg_buffer.len() + entry_offset: entry_offset, + detail_offset: self.detail_buffer.len() }; - /* - TODO: don't actually assert this until asserts are compiled - in/out based on severity, debug/release, etc. + /* TODO: don't actually assert this until asserts are compiled + in/out based on severity, debug/release, etc. This assertion + would wreck the complexity of the lookup. See Rust Issue #3647, #2228, #3627 for related information. - do self.dg_lookup.borrow |arr| { + do self.detail_lookup.borrow |arr| { assert !arr.contains(entry) } */ - self.dg_lookup.push(entry); - self.dg_buffer.push_all(glyphs); + self.detail_lookup.push(entry); + self.detail_buffer.push_all(glyphs); self.lookup_is_sorted = false; } // not pure; may perform a deferred sort. - fn get_glyphs_for_cg(&self, cg_offset: u32, count: uint) -> &[DetailedGlyph] { - assert count > 0 && count < self.dg_buffer.len(); + fn get_detailed_glyphs_for_entry(&self, entry_offset: uint, count: u16) -> &[DetailedGlyph] { + assert count > 0; + assert (count as uint) <= self.detail_buffer.len(); self.ensure_sorted(); let key = DetailedGlyphRecord { - cg_offset: cg_offset, - dg_offset: 0 // unused + entry_offset: entry_offset, + detail_offset: 0 // unused }; - do self.dg_lookup.borrow |records : &[DetailedGlyphRecord]| { + do self.detail_lookup.borrow |records : &[DetailedGlyphRecord]| { match records.binary_search_index(&key) { None => fail ~"Invalid index not found in detailed glyph lookup table!", Some(i) => { - do self.dg_buffer.borrow |glyphs : &[DetailedGlyph]| { - assert i + count < glyphs.len(); + do self.detail_buffer.borrow |glyphs : &[DetailedGlyph]| { + assert i + (count as uint) < glyphs.len(); // return a view into the buffer - vec::view(glyphs, i, count) + vec::view(glyphs, i, count as uint) + } + } + } + } + } + + fn get_detailed_glyph_with_index(&self, entry_offset: uint, detail_offset: u16) -> &DetailedGlyph { + assert (detail_offset as uint) <= self.detail_buffer.len(); + self.ensure_sorted(); + + let key = DetailedGlyphRecord { + entry_offset: entry_offset, + detail_offset: 0 // unused + }; + + do self.detail_lookup.borrow |records : &[DetailedGlyphRecord]| { + match records.binary_search_index(&key) { + None => fail ~"Invalid index not found in detailed glyph lookup table!", + Some(i) => { + do self.detail_buffer.borrow |glyphs : &[DetailedGlyph]| { + assert i + (detail_offset as uint) < glyphs.len(); + &glyphs[i+(detail_offset as uint)] } } } @@ -289,54 +368,252 @@ impl DetailedGlyphStore { return; } - do self.dg_lookup.borrow_mut |arr| { + do self.detail_lookup.borrow_mut |arr| { sort::quick_sort3(arr); }; self.lookup_is_sorted = true; } } -// Public data structure and API for storing glyph data +// This struct is used by GlyphStore clients to provide new glyph data. +// It should be allocated on the stack and passed by reference to GlyphStore. +struct GlyphData { + index: GlyphIndex, + advance: au, + offset: Point2D, + is_missing: bool, + cluster_start: bool, + ligature_start: bool, +} + +pure fn GlyphData(index: GlyphIndex, + advance: au, + offset: Option>, + 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> { + match self { + SimpleGlyphInfo(_, _) => None, + DetailGlyphInfo(store, entry_i, detail_j) => Some(store.detail_store.get_detailed_glyph_with_index(entry_i, detail_j).offset) + } + } + + fn is_ligature_start() -> bool { + match self { + SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i].is_ligature_start(), + DetailGlyphInfo(store, entry_i, _) => store.entry_buffer[entry_i].is_ligature_start() + } + } + + fn is_cluster_start() -> bool { + match self { + SimpleGlyphInfo(store, entry_i) => store.entry_buffer[entry_i].is_cluster_start(), + DetailGlyphInfo(store, entry_i, _) => store.entry_buffer[entry_i].is_cluster_start() + } + } +} + +// Public data structure and API for storing and retrieving glyph data struct GlyphStore { - mut cg_buffer: ~[CompressedGlyph], - dg_store: DetailedGlyphStore, + // we use a DVec here instead of a mut vec, since this is much safer. + entry_buffer: DVec, + detail_store: DetailedGlyphStore, } // Initializes the glyph store, but doesn't actually shape anything. // Use the set_glyph, set_glyphs() methods to store glyph data. -// Use the get_glyph_data method to retrieve glyph data for a char.. -fn GlyphStore(_text: ~str) { +fn GlyphStore(text: &str) -> GlyphStore { + assert text.len() > 0; + let buffer = vec::from_elem(text.len(), InitialGlyphEntry()); + + GlyphStore { + entry_buffer: dvec::from_vec(buffer), + detail_store: DetailedGlyphStore(), + } } impl GlyphStore { - -} + fn add_glyph_for_index(i: uint, data: &GlyphData) { -// XXX: legacy glyphs below. rip out once converted to new glyphs + pure fn glyph_is_compressible(data: &GlyphData) -> bool { + is_simple_glyph_id(data.index) + && is_simple_advance(data.advance) + && data.offset == au::zero_point() + } -/** The position of a glyph on the screen. */ -struct GlyphPos { - advance: Point2D, - offset: Point2D, -} + assert i < self.entry_buffer.len(); -fn GlyphPos(advance: Point2D, offset: Point2D) -> GlyphPos { - GlyphPos { - advance : advance, - offset : offset, - } -} - -/** A single glyph. */ -struct Glyph { - index: u32, - pos: GlyphPos, -} - -fn Glyph(index: u32, pos: GlyphPos) -> Glyph { - Glyph { - index : index, - pos : copy pos, + let entry = match (data.is_missing, glyph_is_compressible(data)) { + (true, _) => MissingGlyphsEntry(1), + (false, true) => { SimpleGlyphEntry(data.index, data.advance) }, + (false, false) => { + let glyph = [DetailedGlyph(data.index, data.advance, data.offset)]; + self.detail_store.add_detailed_glyphs_for_entry(i, glyph); + ComplexGlyphEntry(data.cluster_start, data.ligature_start, 1) + } + }; + + self.entry_buffer.set_elt(i, entry); + } + + fn add_glyphs_for_index(i: uint, data_for_glyphs: &[GlyphData]) { + assert i < self.entry_buffer.len(); + assert data_for_glyphs.len() > 0; + + let glyph_count = data_for_glyphs.len(); + + let first_glyph_data = data_for_glyphs[0]; + let entry = match first_glyph_data.is_missing { + true => MissingGlyphsEntry(glyph_count), + false => { + let glyphs_vec = vec::from_fn(glyph_count, |i| { + DetailedGlyph(data_for_glyphs[i].index, + data_for_glyphs[i].advance, + data_for_glyphs[i].offset) + }); + + self.detail_store.add_detailed_glyphs_for_entry(i, glyphs_vec); + ComplexGlyphEntry(first_glyph_data.cluster_start, + first_glyph_data.ligature_start, + glyph_count) + } + }; + + self.entry_buffer.set_elt(i, entry); + } + + fn iter_glyphs_for_index(&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(&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(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 => {} + }; } } diff --git a/src/servo/text/native_font/ft_native_font.rs b/src/servo/text/native_font/ft_native_font.rs index cf7e660a73c..d0e6f3961e9 100644 --- a/src/servo/text/native_font/ft_native_font.rs +++ b/src/servo/text/native_font/ft_native_font.rs @@ -1,6 +1,7 @@ #[legacy_exports]; export FreeTypeNativeFont, with_test_native_font, create; +use util::*; use vec_as_buf = vec::as_imm_buf; use ptr::{addr_of, null}; use cast::reinterpret_cast; @@ -18,6 +19,14 @@ use freetype::bindgen::{ FT_Set_Char_Size }; +fn float_to_fixed_ft(f: float) -> i32 { + float_to_fixed(26, f) +} + +fn fixed_to_float_ft(f: i32) -> float { + fixed_to_float(26, f) +} + struct FreeTypeNativeFont { /// The font binary. This must stay valid for the lifetime of the font buf: @~[u8], @@ -44,13 +53,13 @@ impl FreeTypeNativeFont { return if idx != 0 as FT_UInt { Some(idx as GlyphIndex) } else { - #warn("Invalid codepoint: %?", codepoint); + debug!("Invalid codepoint: %?", codepoint); None }; } // FIXME: What unit is this returning? Let's have a custom type - fn glyph_h_advance(glyph: GlyphIndex) -> Option { + fn glyph_h_advance(glyph: GlyphIndex) -> Option { assert self.face.is_not_null(); let res = FT_Load_Glyph(self.face, glyph as FT_UInt, 0); if res.succeeded() { @@ -59,13 +68,11 @@ impl FreeTypeNativeFont { let slot: FT_GlyphSlot = reinterpret_cast(&void_glyph); assert slot.is_not_null(); let advance = (*slot).metrics.horiAdvance; - #debug("h_advance for %? is %?", glyph, advance); - // FIXME: Dividing by 64 converts to pixels, which - // is not the unit we should be using - return Some((advance / 64) as int); + debug!("h_advance for %? is %?", glyph, advance); + return Some(fixed_to_float_ft(advance) as FractionalPixel); } } else { - #warn("Unable to load glyph %?. reason: %?", glyph, res); + debug!("Unable to load glyph %?. reason: %?", glyph, res); return None; } } diff --git a/src/servo/text/native_font/quartz_native_font.rs b/src/servo/text/native_font/quartz_native_font.rs index a16ec298a74..0db7a119465 100644 --- a/src/servo/text/native_font/quartz_native_font.rs +++ b/src/servo/text/native_font/quartz_native_font.rs @@ -2,8 +2,9 @@ extern mod cocoa; export QuartzNativeFont, with_test_native_font, create; -use font::FontMetrics; +use font::{FontMetrics, FractionalPixel}; +use au = gfx::geometry; use libc::size_t; use ptr::null; use glyph::GlyphIndex; @@ -130,8 +131,7 @@ impl QuartzNativeFont { return Some(glyphs[0] as GlyphIndex); } - // FIXME: What unit is this returning? Let's have a custom type - fn glyph_h_advance(glyph: GlyphIndex) -> Option { + fn glyph_h_advance(glyph: GlyphIndex) -> Option { use coretext::{CGGlyph, kCTFontDefaultOrientation}; use coretext::coretext::{CTFontGetAdvancesForGlyphs}; @@ -141,7 +141,7 @@ impl QuartzNativeFont { CTFontGetAdvancesForGlyphs(self.ctfont, kCTFontDefaultOrientation, glyph_buf, null(), 1) }; - return Some(advance as int); + return Some(advance as FractionalPixel); } fn get_metrics() -> FontMetrics { @@ -164,6 +164,7 @@ impl QuartzNativeFont { em_ascent: CTFontGetAscent(ctfont) as float * convFactor, em_descent: CTFontGetDescent(ctfont) as float * convFactor, em_height: em_ascent + em_descent, + em_size: au::from_pt(21f), max_advance: bounding_rect.size.width as float * convFactor, } } @@ -174,6 +175,7 @@ fn ctfont_from_cgfont(cgfont: CGFontRef) -> coretext::CTFontRef { use coretext::coretext::CTFontCreateWithGraphicsFont; assert cgfont.is_not_null(); + // TODO: use actual font size here! CTFontCreateWithGraphicsFont(cgfont, 21f as CGFloat, null(), null()) } diff --git a/src/servo/text/shaper.rs b/src/servo/text/shaper.rs index cae01b11f8d..039f0cd341a 100644 --- a/src/servo/text/shaper.rs +++ b/src/servo/text/shaper.rs @@ -1,23 +1,26 @@ extern mod harfbuzz; -export shape_text; - use au = gfx::geometry; +use au::au; +use core::num::from_int; +use font::Font; +use font_cache::FontCache; +use geom::point::Point2D; +use glyph::{GlyphStore, GlyphIndex, GlyphData}; use libc::types::common::c99::int32_t; use libc::{c_uint, c_int, c_void, c_char}; -use font::Font; -use glyph::{Glyph, GlyphPos, GlyphIndex}; use ptr::{null, to_unsafe_ptr, offset}; -use gfx::geometry::au; -use geom::point::Point2D; -use font_cache::FontCache; +use std::arc; +use text_run::TextRun; +use util::*; + use cast::reinterpret_cast; use harfbuzz::{HB_MEMORY_MODE_READONLY, HB_DIRECTION_LTR}; -use harfbuzz::{hb_blob_t, hb_face_t, hb_font_t, hb_buffer_t, +use harfbuzz::{hb_blob_t, hb_face_t, hb_font_t, hb_font_funcs_t, hb_buffer_t, hb_codepoint_t, hb_bool_t, hb_glyph_position_t, - hb_var_int_t, hb_position_t}; + hb_glyph_info_t, hb_var_int_t, hb_position_t}; use harfbuzz::bindgen::{hb_blob_create, hb_blob_destroy, hb_face_create, hb_face_destroy, hb_font_create, hb_font_destroy, @@ -33,75 +36,101 @@ use harfbuzz::bindgen::{hb_blob_create, hb_blob_destroy, hb_font_funcs_set_glyph_func, hb_font_funcs_set_glyph_h_kerning_func}; -#[doc = " +fn float_to_fixed_hb(f: float) -> i32 { + util::float_to_fixed(16, f) +} + +fn fixed_to_float_hb(i: hb_position_t) -> float { + util::fixed_to_float(16, i) +} + +fn fixed_to_rounded_int_hb(f: hb_position_t) -> int { + util::fixed_to_rounded_int(16, f) +} + +/** Calculate the layout metrics associated with a some given text when rendered in a specific font. -"] -fn shape_text(font: &Font, text: &str) -> ~[Glyph] unsafe { - #debug("shaping text '%s'", text); +*/ +pub fn shape_textrun(font: &Font, run: &TextRun) { + debug!("shaping text '%s'", run.text); - let face_blob = vec::as_imm_buf(*(*font).buf(), |buf, len| { - hb_blob_create(reinterpret_cast(&buf), + // TODO: harfbuzz fonts and faces should be cached on the Font object. + // TODO: font tables should be stored in Font object and cached by FontCache (Issue #92) + let face_blob: *hb_blob_t = vec::as_imm_buf(*(*font).fontbuf, |buf: *u8, len: uint| { + hb_blob_create(buf as *c_char, len as c_uint, HB_MEMORY_MODE_READONLY, null(), null()) }); - let hbface = hb_face_create(face_blob, 0 as c_uint); - let hbfont = hb_font_create(hbface); + let hb_face: *hb_face_t = hb_face_create(face_blob, 0 as c_uint); + let hb_font: *hb_font_t = hb_font_create(hb_face); - hb_font_set_ppem(hbfont, 10 as c_uint, 10 as c_uint); - hb_font_set_scale(hbfont, 10 as c_int, 10 as c_int); + // TODO: set font size here, based on Font's size + // Set points-per-em. if zero, performs no hinting in that direction. + hb_font_set_ppem(hb_font, 21 as c_uint, 21 as c_uint); + // Set scaling. Note that this takes 16.16 fixed point. + hb_font_set_scale(hb_font, float_to_fixed_hb(21f) as c_int, float_to_fixed_hb(21f) as c_int); - let funcs = hb_font_funcs_create(); + let funcs: *hb_font_funcs_t = hb_font_funcs_create(); hb_font_funcs_set_glyph_func(funcs, glyph_func, null(), null()); hb_font_funcs_set_glyph_h_advance_func(funcs, glyph_h_advance_func, null(), null()); - hb_font_set_funcs(hbfont, funcs, reinterpret_cast(&to_unsafe_ptr(font)), null()); - let buffer = hb_buffer_create(); + unsafe { + let font_data: *c_void = cast::transmute(font); + hb_font_set_funcs(hb_font, funcs, font_data, null()); + }; - hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); + let hb_buffer: *hb_buffer_t = hb_buffer_create(); + hb_buffer_set_direction(hb_buffer, HB_DIRECTION_LTR); // Using as_buf because it never does a copy - we don't need the trailing null - str::as_buf(text, |ctext, _l| { - hb_buffer_add_utf8(buffer, ctext as *c_char, - text.len() as c_int, + str::as_buf(run.text, |ctext: *u8, _l: uint| { + hb_buffer_add_utf8(hb_buffer, + ctext as *c_char, + run.text.len() as c_int, 0 as c_uint, - text.len() as c_int); + run.text.len() as c_int); }); - hb_shape(hbfont, buffer, null(), 0 as c_uint); + hb_shape(hb_font, hb_buffer, null(), 0 as c_uint); - let info_len = 0 as c_uint; - let info_ = hb_buffer_get_glyph_infos(buffer, to_unsafe_ptr(&info_len)); - assert info_.is_not_null(); - let pos_len = 0 as c_uint; - let pos = hb_buffer_get_glyph_positions(buffer, to_unsafe_ptr(&pos_len)); - assert pos.is_not_null(); + let info_buf_len = 0 as c_uint; + let info_buf = hb_buffer_get_glyph_infos(hb_buffer, to_unsafe_ptr(&info_buf_len)); + assert info_buf.is_not_null(); + let pos_buf_len = 0 as c_uint; + let pos_buf = hb_buffer_get_glyph_positions(hb_buffer, to_unsafe_ptr(&pos_buf_len)); + assert pos_buf.is_not_null(); - assert info_len == pos_len; + assert info_buf_len == pos_buf_len; - let mut glyphs = ~[]; + for uint::range(0u, info_buf_len as uint) |i| { unsafe { + let hb_info: hb_glyph_info_t = *offset(info_buf, i); + let hb_pos: hb_glyph_position_t = *offset(pos_buf, i); + let codepoint = hb_info.codepoint as GlyphIndex; + let advance: au = au::from_frac_px(fixed_to_float_hb(hb_pos.x_advance)); + let offset = match (hb_pos.x_offset, hb_pos.y_offset) { + (0, 0) => None, + (x, y) => Some(Point2D(au::from_frac_px(fixed_to_float_hb(x)), + au::from_frac_px(fixed_to_float_hb(y)))) + }; + // TODO: convert pos.y_advance into offset adjustment + // TODO: handle multiple glyphs per char, ligatures, etc. + // See Issue # + debug!("glyph %?: index %?, advance %?, offset %?", + i, codepoint, advance, offset); - for uint::range(0u, info_len as uint) |i| { - let info_ = offset(info_, i); - let pos = offset(pos, i); - let codepoint = (*info_).codepoint as u32; - let pos = hb_glyph_pos_to_servo_glyph_pos(&*pos); - #debug("glyph %?: codep %?, x_adv %?, y_adv %?, x_off %?, y_of %?", - i, codepoint, pos.advance.x, pos.advance.y, pos.offset.x, pos.offset.y); + let data = GlyphData(codepoint, advance, offset, false, false, false); + run.glyphs.add_glyph_for_index(i, &data); + } /* unsafe */ } - glyphs += ~[Glyph(codepoint, pos)]; - } - - hb_buffer_destroy(buffer); + hb_buffer_destroy(hb_buffer); hb_font_funcs_destroy(funcs); - hb_font_destroy(hbfont); - hb_face_destroy(hbface); + hb_font_destroy(hb_font); + hb_face_destroy(hb_face); hb_blob_destroy(face_blob); - - return glyphs; } extern fn glyph_func(_font: *hb_font_t, @@ -111,17 +140,12 @@ extern fn glyph_func(_font: *hb_font_t, glyph: *mut hb_codepoint_t, _user_data: *c_void) -> hb_bool_t unsafe { - let font: *Font = reinterpret_cast(&font_data); + let font: *Font = cast::transmute(font_data); assert font.is_not_null(); return match (*font).glyph_index(unicode as char) { - Some(g) => { - *glyph = g as hb_codepoint_t; - true - } - None => { - false - } + Some(g) => { *glyph = g as hb_codepoint_t; true }, + None => false } as hb_bool_t; } @@ -129,19 +153,11 @@ extern fn glyph_h_advance_func(_font: *hb_font_t, font_data: *c_void, glyph: hb_codepoint_t, _user_data: *c_void) -> hb_position_t unsafe { - let font: *Font = reinterpret_cast(&font_data); + let font: *Font = cast::transmute(font_data); assert font.is_not_null(); - let h_advance = (*font).glyph_h_advance(glyph as u32); - #debug("h_advance for codepoint %? is %?", glyph, h_advance); - return h_advance as hb_position_t; -} - -fn hb_glyph_pos_to_servo_glyph_pos(hb_pos: &hb_glyph_position_t) -> GlyphPos { - GlyphPos(Point2D(au::from_px(hb_pos.x_advance as int), - au::from_px(hb_pos.y_advance as int)), - Point2D(au::from_px(hb_pos.x_offset as int), - au::from_px(hb_pos.y_offset as int))) + let advance = (*font).glyph_h_advance(glyph as GlyphIndex); + float_to_fixed_hb(advance) } fn should_get_glyph_indexes() { diff --git a/src/servo/text/text_run.rs b/src/servo/text/text_run.rs index e7e908a79ad..8c129c7d95e 100644 --- a/src/servo/text/text_run.rs +++ b/src/servo/text/text_run.rs @@ -1,138 +1,137 @@ +use arc = std::arc; +use arc::ARC; use au = gfx::geometry; +use font::Font; +use font_cache::FontCache; use geom::point::Point2D; use geom::size::Size2D; use gfx::geometry::au; +use glyph::GlyphStore; +use layout::context::LayoutContext; use libc::{c_void}; -use font_cache::FontCache; -use font::Font; -use glyph::Glyph; -use shaper::shape_text; +use servo_util::color; +use shaper::shape_textrun; +use std::arc; -/// A single, unbroken line of text -struct TextRun { +pub struct TextRun { text: ~str, - priv glyphs: ~[Glyph], - priv size_: Size2D, - priv min_break_width_: au, + priv glyphs: GlyphStore, } impl TextRun { - /// The size of the entire TextRun - pure fn size() -> Size2D { self.size_ } - pure fn min_break_width() -> au { self.min_break_width_ } + pure fn glyphs(&self) -> &self/GlyphStore { &self.glyphs } - /// Split a run of text in two - // FIXME: Should be storing a reference to the Font inside - // of the TextRun, but I'm hitting cycle collector bugs - fn split(font: &Font, h_offset: au) -> (TextRun, TextRun) { - assert h_offset >= self.min_break_width(); - assert h_offset <= self.size_.width; + fn min_width_for_range(ctx: &LayoutContext, offset: uint, length: uint) -> au { + assert length > 0; + assert offset < self.text.len(); + assert offset + length <= self.text.len(); - let mut curr_run = ~""; + let mut max_piece_width = au(0); + // TODO: use a real font reference + let font = ctx.font_cache.get_test_font(); + for self.iter_indivisible_pieces_for_range(offset, length) |piece_offset, piece_len| { - for iter_indivisible_slices(font, self.text) |slice| { - let mut candidate = copy curr_run; - - if candidate.is_not_empty() { - str::push_str(&mut candidate, " "); // FIXME: just inserting spaces between words can't be right - } - - str::push_str(&mut candidate, slice); - - let glyphs = shape_text(font, candidate); - let size = glyph_run_size(glyphs); - if size.width <= h_offset { - curr_run = move candidate; - } else { - break; - } - } - - assert curr_run.is_not_empty(); - - let first = move curr_run; - let second_start = match str::find_from(self.text, first.len(), |c| !char::is_whitespace(c)) { - Some(idx) => idx, - None => { - // This will be an empty string - self.text.len() + let metrics = font.measure_text(&self, piece_offset, piece_len); + if metrics.advance > max_piece_width { + max_piece_width = metrics.advance; } }; - let second = str::slice(self.text, second_start, self.text.len()); - return (TextRun(font, first), TextRun(font, second)); + return max_piece_width; } -} -fn TextRun(font: &Font, +text: ~str) -> TextRun { - let glyphs = shape_text(font, text); - let size = glyph_run_size(glyphs); - let min_break_width = calc_min_break_width(font, text); + fn iter_natural_lines_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool) { + assert length > 0; + assert offset < self.text.len(); + assert offset + length <= self.text.len(); - TextRun { - text: text, - glyphs: shape_text(font, text), - size_: size, - min_break_width_: min_break_width - } -} + let mut clump_start = offset; + let mut clump_end = offset; + let mut in_clump = false; -fn glyph_run_size(glyphs: &[Glyph]) -> Size2D { - let height = au::from_px(20); - let pen_start_x = au::from_px(0); - let pen_start_y = height; - let pen_start = Point2D(pen_start_x, pen_start_y); - let pen_end = glyphs.foldl(pen_start, |cur, glyph| { - Point2D(cur.x.add(&glyph.pos.offset.x).add(&glyph.pos.advance.x), - cur.y.add(&glyph.pos.offset.y).add(&glyph.pos.advance.y)) - }); - return Size2D(pen_end.x, pen_end.y); -} - -/// Discovers the width of the largest indivisible substring -fn calc_min_break_width(font: &Font, text: &str) -> au { - let mut max_piece_width = au(0); - for iter_indivisible_slices(font, text) |slice| { - let glyphs = shape_text(font, slice); - let size = glyph_run_size(glyphs); - if size.width > max_piece_width { - max_piece_width = size.width + // clump non-linebreaks of nonzero length + for uint::range(offset, offset + length) |i| { + match (self.glyphs.char_is_newline(i), in_clump) { + (false, true) => { clump_end = i; } + (false, false) => { in_clump = true; clump_start = i; clump_end = i; } + (true, false) => { /* chomp whitespace */ } + (true, true) => { + in_clump = false; + // don't include the linebreak 'glyph' + // (we assume there's one GlyphEntry for a newline, and no actual glyphs) + if !f(clump_start, clump_end - clump_start + 1) { break } + } + } + } + + // flush any remaining chars as a line + if in_clump { + clump_end = offset + length - 1; + f(clump_start, clump_end - clump_start + 1); } } - return max_piece_width; + + pure fn iter_indivisible_pieces_for_range(&self, offset: uint, length: uint, f: fn(uint, uint) -> bool) { + assert length > 0; + assert offset < self.text.len(); + assert offset + length <= self.text.len(); + + //TODO: need a more sophisticated model of words and possible breaks + let text = str::view(self.text, offset, length); + + let mut clump_start = offset; + + loop { + // clump contiguous non-whitespace + match str::find_from(text, clump_start, |c| !char::is_whitespace(c)) { + Some(clump_end) => { + if !f(clump_start, clump_end - clump_start + 1) { break } + clump_start = clump_end + 1; + // reached end + if clump_start == offset + length { break } + }, + None => { + // nothing left, flush last piece containing only spaces + if clump_start < offset + length { + let clump_end = offset + length - 1; + f(clump_start, clump_end - clump_start + 1); + } + break + } + }; + + // clump contiguous whitespace + match str::find_from(text, clump_start, |c| char::is_whitespace(c)) { + Some(clump_end) => { + if !f(clump_start, clump_end - clump_start + 1) { break } + clump_start = clump_end + 1; + // reached end + if clump_start == offset + length { break } + } + None => { + // nothing left, flush last piece containing only spaces + if clump_start < offset + length { + let clump_end = offset + length - 1; + f(clump_start, clump_end - clump_start + 1); + } + break + } + } + } + } +} + +fn TextRun(font: &Font, text: ~str) -> TextRun { + let glyph_store = GlyphStore(text); + let run = TextRun { + text: text, + glyphs: glyph_store, + }; + + shape_textrun(font, &run); + return run; } /// Iterates over all the indivisible substrings -fn iter_indivisible_slices(_font: &Font, text: &r/str, - f: fn((&r/str)) -> bool) { - - let mut curr = text; - loop { - match str::find(curr, |c| !char::is_whitespace(c) ) { - Some(idx) => { - curr = str::view(curr, idx, curr.len()); - } - None => { - // Everything else is whitespace - break - } - } - - match str::find(curr, |c| char::is_whitespace(c) ) { - Some(idx) => { - let piece = str::view(curr, 0, idx); - if !f(piece) { break } - curr = str::view(curr, idx, curr.len()); - } - None => { - assert curr.is_not_empty(); - if !f(curr) { break } - // This is the end of the string - break; - } - } - } -} - #[test] fn test_calc_min_break_width1() { let flib = FontCache(); diff --git a/src/servo/text/util.rs b/src/servo/text/util.rs index b7cf27d864b..faec358750a 100644 --- a/src/servo/text/util.rs +++ b/src/servo/text/util.rs @@ -1,7 +1,22 @@ -export true_type_tag; +pub fn float_to_fixed(before: int, f: float) -> i32 { + (1i32 << before) * (f as i32) +} -#[doc = "Generate a 32-bit TrueType tag from its 4 charecters"] -fn true_type_tag(a: char, b: char, c: char, d: char) -> u32 { +pub fn fixed_to_float(before: int, f: i32) -> float { + f as float * 1.0f / ((1i32 << before) as float) +} + +pub fn fixed_to_rounded_int(before: int, f: i32) -> int { + let half = 1i32 << (before-1); + if f > 0i32 { + ((half + f) >> before) as int + } else { + -((half - f) >> before) as int + } +} + +/* Generate a 32-bit TrueType tag from its 4 charecters */ +pub fn true_type_tag(a: char, b: char, c: char, d: char) -> u32 { (a << 24 | b << 16 | c << 8 | d) as u32 }