Extract mono-cache implementation; lay groundwork for private font matching.

This commit is contained in:
Brian J. Burg 2012-11-07 12:04:17 -08:00
parent a3f4b52c90
commit b8d7824067
18 changed files with 372 additions and 88 deletions

View file

@ -10,7 +10,11 @@ pub use dl = display_list;
pub use display_list::DisplayList;
pub use font::Font;
pub use font_cache::FontCache;
pub use font::FontDescriptor;
pub use font::FontGroup;
pub use font::FontSelector;
pub use font::FontStyle;
pub use font::RunMetrics;
pub use font_context::FontContext;
pub use font_matcher::FontMatcher;
pub use geometry::Au;

View file

@ -49,7 +49,7 @@ impl DisplayItem {
match *self {
SolidColor(_, color) => ctx.draw_solid_color(&self.d().bounds, color),
Text(_, run, range) => {
let new_run = @run.deserialize(ctx.font_cache);
let new_run = @run.deserialize(ctx.font_ctx);
let font = new_run.font;
let origin = self.d().bounds.origin;
let baseline_origin = Point2D(origin.x, origin.y + font.metrics.ascent);

View file

@ -38,23 +38,147 @@ enum CSSFontWeight {
FontWeight800,
FontWeight900,
}
pub impl CSSFontWeight : cmp::Eq;
struct FontStyle {
// TODO: eventually this will be split into the specified and used
// font styles. specified contains uninterpreted CSS font property
// values, while 'used' is attached to gfx::Font to descript the
// instance's properties.
//
// For now, the cases are differentiated with a typedef
pub struct FontStyle {
pt_size: float,
weight: CSSFontWeight,
italic: bool,
oblique: bool,
families: ~str,
// TODO: font-stretch, text-decoration, font-variant, size-adjust
}
struct FontFaceProperties {
// TODO(Issue #181): use deriving for trivial cmp::Eq implementations
pub impl FontStyle : cmp::Eq {
pure fn eq(other: &FontStyle) -> bool {
use std::cmp::FuzzyEq;
self.pt_size.fuzzy_eq(&other.pt_size) &&
self.weight == other.weight &&
self.italic == other.italic &&
self.oblique == other.oblique &&
self.families == other.families
}
pure fn ne(other: &FontStyle) -> bool { !self.eq(other) }
}
pub type SpecifiedFontStyle = FontStyle;
pub type UsedFontStyle = FontStyle;
// TODO: move me to layout
struct ResolvedFont {
group: @FontGroup,
style: SpecifiedFontStyle,
}
// FontDescriptor serializes a specific font and used font style
// options, such as point size.
// It's used to swizzle/unswizzle gfx::Font instances when
// communicating across tasks, such as the display list between layout
// and render tasks.
pub struct FontDescriptor {
style: UsedFontStyle,
selector: FontSelector,
}
// TODO(Issue #181): use deriving for trivial cmp::Eq implementations
pub impl FontDescriptor : cmp::Eq {
pure fn eq(other: &FontDescriptor) -> bool {
self.style == other.style &&
self.selector == other.selector
}
pure fn ne(other: &FontDescriptor) -> bool { !self.eq(other) }
}
pub impl FontDescriptor {
static pure fn new(style: &UsedFontStyle, selector: &FontSelector) -> FontDescriptor {
FontDescriptor {
style: copy *style,
selector: copy *selector,
}
}
}
// A FontSelector is a platform-specific strategy for serializing face names.
pub enum FontSelector {
SelectorPlatformName(~str),
SelectorStubDummy, // aka, use Josephin Sans
}
// TODO(Issue #181): use deriving for trivial cmp::Eq implementations
pub impl FontSelector : cmp::Eq {
pure fn eq(other: &FontSelector) -> bool {
match (self, *other) {
(SelectorStubDummy, SelectorStubDummy) => true,
(SelectorPlatformName(a), SelectorPlatformName(b)) => a == b,
_ => false
}
}
pure fn ne(other: &FontSelector) -> bool { !self.eq(other) }
}
// Holds a specific font family, and the various
pub struct FontFamily {
family_name: @str,
face_name: ~str,
priv weight: u16,
priv italic: bool,
entries: ~[@FontEntry],
}
// This struct is the result of mapping a specified FontStyle into the
// available fonts on the system. It contains an ordered list of font
// instances to be used in case the prior font cannot be used for
// rendering the specified language.
// The ordering of font instances is mainly decided by the CSS
// 'font-family' property. The last font is a system fallback font.
pub struct FontGroup {
families: @str,
// style of the first western font in group, which is
// used for purposes of calculating text run metrics.
style: UsedFontStyle,
fonts: ~[@Font],
}
pub impl FontGroup {
static fn new(families: @str, style: &UsedFontStyle, fonts: ~[@Font]) -> FontGroup {
FontGroup {
families: families,
style: copy *style,
fonts: move fonts,
}
}
}
// This struct summarizes an available font's features. In the future,
// this will include fiddly settings such as special font table handling.
// In the common case, each FontFamily will have a singleton FontEntry, or
// it will have the standard four faces: Normal, Bold, Italic, BoldItalic.
struct FontEntry {
family: @FontFamily,
face_name: ~str,
priv weight: CSSFontWeight,
priv italic: bool,
// TODO: array of OpenType features, etc.
}
impl FontEntry {
pure fn is_bold() -> bool {
match self.weight {
FontWeight900 | FontWeight800 | FontWeight700 | FontWeight600 => true,
_ => false
}
}
impl FontFaceProperties {
pure fn is_bold() -> bool { self.weight >= (500 as u16) }
pure fn is_italic() -> bool { self.italic }
}
@ -69,7 +193,7 @@ struct RunMetrics {
}
/**
A font handle. Layout can use this to calculate glyph metrics
A font instance. Layout can use this to calculate glyph metrics
and the renderer can use it to render text.
*/
struct Font {
@ -77,7 +201,7 @@ struct Font {
priv handle: FontHandle,
priv mut azure_font: Option<AzScaledFontRef>,
priv mut shaper: Option<@Shaper>,
style: FontStyle,
style: UsedFontStyle,
metrics: FontMetrics,
drop {
@ -88,7 +212,7 @@ struct Font {
impl Font {
// TODO: who should own fontbuf?
static fn new(fontbuf: @~[u8], handle: FontHandle, style: FontStyle) -> Font {
static fn new(fontbuf: @~[u8], handle: FontHandle, style: UsedFontStyle) -> Font {
let metrics = handle.get_metrics();
Font {
@ -214,6 +338,7 @@ pub trait FontMethods {
fn draw_text_into_context(rctx: &RenderContext, run: &TextRun, range: Range, baseline_origin: Point2D<Au>);
fn measure_text(&TextRun, Range) -> RunMetrics;
fn shape_text(@self, &str) -> GlyphStore;
fn get_descriptor() -> FontDescriptor;
fn buf(&self) -> @~[u8];
// these are used to get glyphs and advances in the case that the
@ -315,6 +440,12 @@ pub impl Font : FontMethods {
return move store;
}
fn get_descriptor() -> FontDescriptor {
// TODO(Issue #174): implement by-platform-name FontSelectors,
// probably by adding such an API to FontHandle.
FontDescriptor::new(&font_context::dummy_style(), &SelectorStubDummy)
}
fn buf(&self) -> @~[u8] {
self.fontbuf
}

View file

@ -1,14 +1,8 @@
/*
use font::{Font, FontStyle, FontWeight300};
use native::{FontHandle, FontContext};
// TODO(Issue #164): delete, and get default font from font list
const TEST_FONT: [u8 * 33004] = #include_bin("JosefinSans-SemiBold.ttf");
fn test_font_bin() -> ~[u8] {
return vec::from_fn(33004, |i| TEST_FONT[i]);
}
// Dummy font cache.
// Font cache that reuses gfx::Font instances.
struct FontCache {
fctx: @FontContext,
@ -24,36 +18,19 @@ impl FontCache {
}
pub fn get_test_font(@self) -> @Font {
let dummy_style = FontStyle {
pt_size: 40f,
weight: FontWeight300,
italic: false,
oblique: false
};
return match self.cached_font {
Some(font) => font,
None => match self.get_font(&dummy_style) {
Ok(font) => { self.cached_font = Some(font); font }
Err(*) => /* FIXME */ fail
Err(*) => fail
}
}
}
// TODO: maybe FontStyle should be canonicalized when used in FontCache?
priv fn create_font(style: &FontStyle) -> Result<@Font, ()> {
let font_bin = @test_font_bin();
let native_font = FontHandle::new(self.fctx, font_bin, style.pt_size);
let native_font = if native_font.is_ok() {
result::unwrap(move native_font)
} else {
return Err(native_font.get_err());
};
return Ok(@Font::new(font_bin, move native_font, copy *style));
}
pub fn get_font(@self, style: &FontStyle) -> Result<@Font, ()> {
self.create_font(style)
}
}
*/

View file

@ -1,3 +1,34 @@
use dvec::DVec;
use util::cache;
use gfx::{
FontDescriptor,
FontSelector,
FontStyle,
};
use gfx::font::{SelectorPlatformName, SelectorStubDummy, SpecifiedFontStyle};
use gfx::native::FontHandle;
// TODO(Issue #164): delete, and get default font from font list
const TEST_FONT: [u8 * 33004] = #include_bin("JosefinSans-SemiBold.ttf");
fn test_font_bin() -> ~[u8] {
return vec::from_fn(33004, |i| TEST_FONT[i]);
}
// TODO(Rust #3934): creating lots of new dummy styles is a workaround
// for not being able to store symbolic enums in top-level constants.
pub fn dummy_style() -> FontStyle {
use gfx::font::FontWeight300;
return FontStyle {
pt_size: 20f,
weight: FontWeight300,
italic: false,
oblique: false,
families: ~"Helvetica, serif",
}
}
// TODO(Issue #163): this is a workaround for static methods and
// typedefs not working well together. It should be removed.
@ -5,21 +36,91 @@
// to conditionally define the entire impl.
#[cfg(target_os = "macos")]
type FontContext/& = quartz::font_context::QuartzFontContext;
type FontContextHandle/& = quartz::font_context::QuartzFontContext;
#[cfg(target_os = "linux")]
type FontContext/& = freetype::font_context::FreeTypeFontContext;
type FontContextHandle/& = freetype::font_context::FreeTypeFontContext;
#[cfg(target_os = "macos")]
pub impl FontContext {
static pub fn new() -> FontContext {
pub impl FontContextHandle {
static pub fn new() -> FontContextHandle {
quartz::font_context::QuartzFontContext::new()
}
}
#[cfg(target_os = "linux")]
pub impl FontContext {
static pub fn new() -> FontContext {
pub impl FontContextHandle {
static pub fn new() -> FontContextHandle {
freetype::font_context::FreeTypeFontContext::new()
}
}
pub struct FontContext {
instance_cache: cache::MonoCache<FontDescriptor, @Font>,
handle: FontContextHandle,
}
pub impl FontContext {
static fn new() -> FontContext {
FontContext {
// TODO(Rust #3902): remove extraneous type parameters once they are inferred correctly.
instance_cache: cache::new::<FontDescriptor, @Font, cache::MonoCache<FontDescriptor, @Font>>(10),
handle: FontContextHandle::new()
}
}
fn get_resolved_font_for_style(style: &SpecifiedFontStyle) -> @FontGroup {
// TODO(Issue #178, E): implement a cache of FontGroup instances.
self.create_font_group(style)
}
fn get_font_by_descriptor(desc: &FontDescriptor) -> Result<@Font, ()> {
match self.instance_cache.find(desc) {
Some(f) => Ok(f),
None => {
let result = self.create_font_instance(desc);
match result {
Ok(font) => {
self.instance_cache.insert(desc, font);
}, _ => {}
};
result
}
}
}
priv fn create_font_group(style: &SpecifiedFontStyle) -> @FontGroup {
// TODO(Issue #178, D): implement private font matching
// TODO(Issue #174): implement by-platform-name FontSelectors
let desc = FontDescriptor::new(&font_context::dummy_style(), &SelectorStubDummy);
let fonts = DVec();
match self.get_font_by_descriptor(&desc) {
Ok(instance) => fonts.push(instance),
Err(()) => {}
}
assert fonts.len() > 0;
// TODO(Issue #179): Split FontStyle into specified and used styles
let used_style = copy *style;
@FontGroup::new(style.families.to_managed(), &used_style, dvec::unwrap(move fonts))
}
priv fn create_font_instance(desc: &FontDescriptor) -> Result<@Font, ()> {
match desc.selector {
SelectorStubDummy => {
let font_bin = @test_font_bin();
let handle = FontHandle::new(&self.handle, font_bin, desc.style.pt_size);
let handle = if handle.is_ok() {
result::unwrap(move handle)
} else {
return Err(handle.get_err());
};
return Ok(@Font::new(font_bin, move handle, copy desc.style));
},
// TODO(Issue #174): implement by-platform-name font selectors.
SelectorPlatformName(_) => { fail ~"FontContext::create_font_instance() can't yet handle SelectorPlatformName." }
}
}
}

View file

@ -20,14 +20,14 @@ pub type FontHandle/& = freetype::font_handle::FreeTypeFontHandle;
// to conditionally define the entire impl.
#[cfg(target_os = "macos")]
impl FontHandle {
static pub fn new(fctx: &native::FontContext, buf: @~[u8], pt_size: float) -> Result<FontHandle, ()> {
static pub fn new(fctx: &native::FontContextHandle, buf: @~[u8], pt_size: float) -> Result<FontHandle, ()> {
quartz::font_handle::QuartzFontHandle::new(fctx, buf, pt_size)
}
}
#[cfg(target_os = "linux")]
impl FontHandle {
static pub fn new(fctx: &native::FontContext, buf: @~[u8], pt_size: float) -> Result<FontHandle, ()> {
static pub fn new(fctx: &native::FontContextHandle, buf: @~[u8], pt_size: float) -> Result<FontHandle, ()> {
freetype::font_handle::FreeTypeFontHandle::new(fctx, buf, pt_size)
}
}

View file

@ -1,11 +1,9 @@
extern mod harfbuzz;
use gfx::au;
use gfx::{
au,
Au,
Font,
FontCache,
};
use geom::point::Point2D;

View file

@ -5,4 +5,4 @@ Note that you still must define each of the files as a module in
servo.rc. This is not ideal and may be changed in the future. */
pub use font_handle::FontHandle;
pub use font_context::FontContext;
pub use font_context::FontContextHandle;

View file

@ -1,12 +1,12 @@
use au = geometry;
use compositor::LayerBuffer;
use gfx::au;
use gfx::{
Au,
Font,
FontCache,
FontContext,
TextRun,
};
use image::base::Image;
use au::Au;
use util::range::Range;
use cairo::cairo_hl::ImageSurface;
@ -23,7 +23,7 @@ use azure::azure_hl::{DrawTarget, Linear};
struct RenderContext {
canvas: &LayerBuffer,
font_cache: @FontCache,
font_ctx: @FontContext,
}
impl RenderContext {

View file

@ -9,7 +9,6 @@ use geom::matrix2d::Matrix2D;
use dl = display_list;
use gfx::{
FontCache,
FontContext,
RenderContext,
RenderLayer,
@ -42,7 +41,7 @@ pub fn RenderTask<C: Compositor Send>(compositor: C) -> RenderTask {
port: po,
compositor: move compositor,
mut layer_buffer_set_port: Cell(move layer_buffer_set_port),
font_cache: @FontCache::new(@FontContext::new()),
font_ctx: @FontContext::new(),
}.start();
}
}
@ -51,7 +50,7 @@ priv struct Renderer<C: Compositor Send> {
port: comm::Port<Msg>,
compositor: C,
layer_buffer_set_port: Cell<pipes::Port<LayerBufferSet>>,
font_cache: @FontCache
font_ctx: @FontContext
}
impl<C: Compositor Send> Renderer<C> {
@ -97,7 +96,7 @@ impl<C: Compositor Send> Renderer<C> {
|render_layer, layer_buffer| {
let ctx = RenderContext {
canvas: layer_buffer,
font_cache: self.font_cache
font_ctx: self.font_ctx
};
// Apply the translation to render the tile we want.

View file

@ -1,11 +1,15 @@
use arc = std::arc;
use arc::ARC;
use au = gfx::geometry;
use font::{RunMetrics, Font};
use font_cache::FontCache;
use geom::point::Point2D;
use geom::size::Size2D;
use gfx::geometry::Au;
use gfx::au;
use gfx::{
Au,
Font,
FontContext,
FontDescriptor,
RunMetrics,
};
use glyph::GlyphStore;
use layout::context::LayoutContext;
use libc::{c_void};
@ -23,16 +27,20 @@ pub struct TextRun {
// we instead use ARC<TextRun> everywhere.
pub struct SendableTextRun {
text: ~str,
font_descriptor: (),
font: FontDescriptor,
priv glyphs: GlyphStore,
}
impl SendableTextRun {
pub fn deserialize(&self, cache: @FontCache) -> TextRun {
pub fn deserialize(&self, fctx: @FontContext) -> TextRun {
let font = match fctx.get_font_by_descriptor(&self.font) {
Ok(f) => f,
Err(_) => fail fmt!("Font descriptor deserialization failed! desc=%?", self.font)
};
TextRun {
text: copy self.text,
// TODO: actually deserialize a font descriptor thingy
font: cache.get_test_font(),
font: font,
glyphs: copy self.glyphs
}
}
@ -49,11 +57,10 @@ impl TextRun {
return move run;
}
pub pure fn serialize(&self, _cache: @FontCache) -> SendableTextRun {
pub fn serialize(&self) -> SendableTextRun {
SendableTextRun {
text: copy self.text,
// TODO: actually serialize a font descriptor thingy
font_descriptor: (),
font: self.font.get_descriptor(),
glyphs: copy self.glyphs,
}
}

View file

@ -409,9 +409,7 @@ impl RenderBox : RenderBoxMethods {
match *self {
UnscannedTextBox(*) => fail ~"Shouldn't see unscanned boxes here.",
TextBox(_,d) => {
list.append_item(~DisplayItem::new_Text(&abs_box_bounds,
~d.run.serialize(builder.ctx.font_cache),
d.range));
list.append_item(~DisplayItem::new_Text(&abs_box_bounds, ~d.run.serialize(), d.range));
// debug frames for text box bounds
debug!("%?", {
list.append_item(~DisplayItem::new_Border(&abs_box_bounds, au::from_px(1),

View file

@ -1,7 +1,7 @@
use geom::rect::Rect;
use gfx::{
Au,
FontCache,
FontContext,
};
use resource::local_image_cache::LocalImageCache;
use std::net::url::Url;
@ -9,7 +9,7 @@ use std::net::url::Url;
/* Represents layout task context. */
struct LayoutContext {
font_cache: @FontCache,
font_ctx: @FontContext,
image_cache: @LocalImageCache,
doc_url: Url,
screen_size: Rect<Au>

View file

@ -7,6 +7,7 @@ use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use gfx::display_list::{DisplayList, DisplayListBuilder};
use gfx::font::FontStyle;
use gfx::geometry::Au;
use layout::box::*;
use layout::context::LayoutContext;
@ -235,8 +236,13 @@ impl TextRunScanner {
// TODO(Issue #115): use actual CSS 'white-space' property of relevant style.
let compression = CompressWhitespaceNewline;
let transformed_text = transform_text(text, compression);
// TODO(Issue #116): use actual font for corresponding DOM node to create text run.
let run = @TextRun::new(ctx.font_cache.get_test_font(), move transformed_text);
// TODO(Issue #116): use actual font and style for corresponding
// DOM node to create text run.
// TODO(Issue #177): text run creation must account for text-renderability by fontgroup fonts.
// this is probably achieved by creating fontgroup above, and then letting FontGroup decide
// which Font to stick into the TextRun.
let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&gfx::font_context::dummy_style());
let run = @TextRun::new(fontgroup.fonts[0], move transformed_text);
debug!("TextRunScanner: pushing single text box in range: %?", self.clump);
let new_box = layout::text::adapt_textbox_with_range(in_boxes[self.clump.begin()].d(), run,
Range(0, run.text.len()));
@ -269,7 +275,11 @@ impl TextRunScanner {
// create the run, then make new boxes with the run and adjusted text indices
// TODO(Issue #116): use actual font for corresponding DOM node to create text run.
let run = @gfx::TextRun::new(ctx.font_cache.get_test_font(), move run_str);
// TODO(Issue #177): text run creation must account for text-renderability by fontgroup fonts.
// this is probably achieved by creating fontgroup above, and then letting FontGroup decide
// which Font to stick into the TextRun.
let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&gfx::font_context::dummy_style());
let run = @TextRun::new(fontgroup.fonts[0], move run_str);
debug!("TextRunScanner: pushing box(es) in range: %?", self.clump);
for self.clump.eachi |i| {
let range = new_ranges[i - self.clump.begin()];

View file

@ -11,11 +11,10 @@ use dom::node::{Node, LayoutData};
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use gfx::{au, dl};
use gfx::{
au, dl,
Au,
DisplayList,
FontCache,
FontContext,
FontMatcher,
RenderLayer,
@ -81,7 +80,7 @@ struct Layout {
local_image_cache: @LocalImageCache,
from_content: comm::Port<Msg>,
font_cache: @FontCache,
font_ctx: @FontContext,
font_matcher: @FontMatcher,
// This is used to root auxilliary RCU reader data
layout_refs: DVec<@LayoutData>,
@ -100,7 +99,7 @@ fn Layout(render_task: RenderTask,
local_image_cache: @LocalImageCache(move image_cache_task),
from_content: from_content,
font_matcher: @FontMatcher::new(fctx),
font_cache: @FontCache::new(fctx),
font_ctx: fctx,
layout_refs: DVec(),
css_select_ctx: Mut(new_css_select_ctx())
}
@ -169,7 +168,7 @@ impl Layout {
let layout_ctx = LayoutContext {
image_cache: self.local_image_cache,
font_cache: self.font_cache,
font_ctx: self.font_ctx,
doc_url: move doc_url,
screen_size: Rect(Point2D(Au(0), Au(0)), screen_size)
};

View file

@ -142,6 +142,7 @@ pub mod resource {
pub mod util {
pub mod actor;
pub mod cache;
pub mod range;
pub mod text;
pub mod time;

59
src/servo/util/cache.rs Normal file
View file

@ -0,0 +1,59 @@
use core::cmp::*;
trait Cache<K: Copy Eq, V: Copy> {
static fn new(size: uint) -> self;
fn insert(key: &K, value: V);
fn find(key: &K) -> Option<V>;
fn find_or_create(key: &K, blk: pure fn&(&K) -> V) -> V;
fn evict_all();
}
pub struct MonoCache<K: Copy Eq, V: Copy> {
mut entry: Option<(K,V)>,
}
pub impl<K: Copy Eq, V: Copy> MonoCache<K,V> : Cache<K,V> {
static fn new(_size: uint) -> MonoCache<K,V> {
MonoCache { entry: None }
}
fn insert(key: &K, value: V) {
self.entry = Some((copy *key, value));
}
fn find(key: &K) -> Option<V> {
match self.entry {
None => None,
Some((ref k,v)) => if *k == *key { Some(v) } else { None }
}
}
fn find_or_create(key: &K, blk: pure fn&(&K) -> V) -> V {
return match self.find(key) {
None => {
let value = blk(key);
self.entry = Some((copy *key, copy value));
move value
},
Some(v) => v
};
}
fn evict_all() {
self.entry = None;
}
}
#[test]
fn test_monocache() {
// TODO: this is hideous because of Rust Issue #3902
let cache = cache::new::<uint, @str, MonoCache<uint, @str>>(10);
let one = @"one";
let two = @"two";
cache.insert(1, one);
assert cache.find(1).is_some();
assert cache.find(2).is_none();
cache.find_or_create(2, |_v| { two });
assert cache.find(2).is_some();
assert cache.find(1).is_none();
}

View file

@ -162,7 +162,7 @@ fn test_transform_discard_newline() {
~"foo bar baz",
~"foobarbaz"];
assert core::vec::same_length(test_strs, oracle_strs);
assert test_strs.len() == oracle_strs.len();
let mode = DiscardNewline;
for uint::range(0, test_strs.len()) |i| {
@ -188,7 +188,7 @@ fn test_transform_compress_whitespace() {
~"foo bar baz",
~"foobarbaz\n\n"];
assert core::vec::same_length(test_strs, oracle_strs);
assert test_strs.len() == oracle_strs.len();
let mode = CompressWhitespace;
for uint::range(0, test_strs.len()) |i| {
@ -214,7 +214,7 @@ fn test_transform_compress_whitespace_newline() {
~"foo bar baz",
~"foobarbaz "];
assert core::vec::same_length(test_strs, oracle_strs);
assert test_strs.len() == oracle_strs.len();
let mode = CompressWhitespaceNewline;
for uint::range(0, test_strs.len()) |i| {