-
-
Notifications
You must be signed in to change notification settings - Fork 3k
/
text_run.rs
203 lines (178 loc) · 6.7 KB
/
text_run.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
use font_context::FontContext;
use geometry::Au;
use glyph::GlyphStore;
use servo_gfx_font::{Font, FontDescriptor, RunMetrics};
use servo_gfx_util::range::{Range, MutableRange};
use core::libc::{c_void};
use geom::point::Point2D;
use geom::size::Size2D;
use std::arc;
use std::arc::ARC;
pub struct TextRun {
text: ~str,
font: @Font,
priv glyphs: GlyphStore,
}
// This is a hack until TextRuns are normally sendable, or
// we instead use ARC<TextRun> everywhere.
pub struct SendableTextRun {
text: ~str,
font: FontDescriptor,
priv glyphs: GlyphStore,
}
impl SendableTextRun {
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,
font: font,
glyphs: copy self.glyphs
}
}
}
impl TextRun {
static fn new(font: @Font, text: ~str) -> TextRun {
let glyph_store = font.shape_text(text);
let run = TextRun {
text: move text,
font: font,
glyphs: move glyph_store,
};
return move run;
}
pub fn serialize(&self) -> SendableTextRun {
SendableTextRun {
text: copy self.text,
font: self.font.get_descriptor(),
glyphs: copy self.glyphs,
}
}
pure fn glyphs(&self) -> &self/GlyphStore { &self.glyphs }
pure fn range_is_trimmable_whitespace(&self, range: &const MutableRange) -> bool {
let mut i = range.begin();
while i < range.end() {
// jump i to each new char
let {ch, next} = str::char_range_at(self.text, i);
match ch {
' ' | '\t' | '\r' => {},
_ => { return false; }
}
i = next;
}
return true;
}
fn metrics_for_range(&self, range: &const MutableRange) -> RunMetrics {
self.font.measure_text(self, range)
}
fn min_width_for_range(&self, range: &const MutableRange) -> Au {
assert range.is_valid_for_string(self.text);
let mut max_piece_width = Au(0);
for self.iter_indivisible_pieces_for_range(range) |piece_range| {
let metrics = self.font.measure_text(self, piece_range);
max_piece_width = Au::max(max_piece_width, metrics.advance_width);
}
return max_piece_width;
}
fn iter_natural_lines_for_range(&self, range: &const MutableRange, f: fn(&const MutableRange) -> bool) {
assert range.is_valid_for_string(self.text);
let mut clump = MutableRange::new(range.begin(), 0);
let mut in_clump = false;
// clump non-linebreaks of nonzero length
for range.eachi |i| {
match (self.glyphs.char_is_newline(i), in_clump) {
(false, true) => { clump.extend_by(1); }
(false, false) => { in_clump = true; clump.reset(i, 1); }
(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(&const clump) { break }
}
}
}
// flush any remaining chars as a line
if in_clump {
clump.extend_to(range.end());
f(&const clump);
}
}
fn iter_indivisible_pieces_for_range(&self, range: &const MutableRange, f: fn(&const MutableRange) -> bool) {
assert range.is_valid_for_string(self.text);
let mut clump = MutableRange::new(range.begin(), 0);
loop {
// find next non-whitespace byte index, then clump all whitespace before it.
match str::find_between(self.text, clump.begin(), range.end(), |c| !char::is_whitespace(c)) {
Some(nonws_char_offset) => {
clump.extend_to(nonws_char_offset);
if !f(&const clump) { break }
clump.reset(clump.end(), 0);
},
None => {
// nothing left, flush last piece containing only whitespace
if clump.end() < range.end() {
clump.extend_to(range.end());
f(&const clump);
break;
}
}
};
// find next whitespace byte index, then clump all non-whitespace before it.
match str::find_between(self.text, clump.begin(), range.end(), |c| char::is_whitespace(c)) {
Some(ws_char_offset) => {
clump.extend_to(ws_char_offset);
if !f(&const clump) { break }
clump.reset(clump.end(), 0);
}
None => {
// nothing left, flush last piece containing only non-whitespaces
if clump.end() < range.end() {
clump.extend_to(range.end());
f(&const clump);
break;
}
}
}
}
}
}
// this test can't run until LayoutContext is removed as an argument
// to min_width_for_range.
/*
#[test]
fn test_calc_min_break_width() {
fn test_min_width_for_run(text: ~str, width: Au) {
let flib = FontCache();
let font = flib.get_test_font();
let run = TextRun(font, text);
run.min_width_for_range(0, text.len())
}
test_min_width_for_run(~"firecracker", au::from_px(84));
test_min_width_for_run(~"firecracker yumyum", au::from_px(84));
test_min_width_for_run(~"yumyum firecracker", au::from_px(84));
test_min_width_for_run(~"yumyum firecracker yumyum", au::from_px(84));
}
*/
/*#[test]
#[ignore]
fn test_iter_indivisible_pieces() {
fn test_pieces(text: ~str, res: ~[~str]) {
let flib = FontCache();
let font = flib.get_test_font();
let run = TextRun::new(font, copy text);
let mut slices : ~[~str] = ~[];
for run.iter_indivisible_pieces_for_range(Range(0, text.len())) |subrange| {
slices.push(str::slice(text, subrange.begin(), subrange.length()));
}
assert slices == res;
}
test_pieces(~"firecracker yumyum woopwoop", ~[~"firecracker", ~" ", ~"yumyum", ~" ", ~"woopwoop"]);
test_pieces(~"firecracker yumyum ", ~[~"firecracker", ~" ", ~"yumyum", ~" "]);
test_pieces(~" firecracker yumyum", ~[~" ", ~"firecracker", ~" ", ~"yumyum"]);
test_pieces(~" ", ~[~" "]);
test_pieces(~"", ~[]);
}
*/