mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
layout: Implement text-align: justify
and text-justify
per
CSS-TEXT-3 § 7.3. `text-justify: distribute` is not supported. The behavior of `text-justify: none` does not seem to match what Firefox and Chrome do, but it seems to match the spec. Closes #213.
This commit is contained in:
parent
f58a129251
commit
5fdaba05a6
17 changed files with 528 additions and 63 deletions
|
@ -2,19 +2,17 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use util::vec::*;
|
||||
use util::range;
|
||||
use util::range::{Range, RangeIndex, EachIndex};
|
||||
use util::geometry::Au;
|
||||
|
||||
use geom::point::Point2D;
|
||||
use std::cmp::{Ordering, PartialOrd};
|
||||
use std::iter::repeat;
|
||||
use std::num::{ToPrimitive, NumCast};
|
||||
use std::mem;
|
||||
use std::num::{ToPrimitive, NumCast};
|
||||
use std::ops::{Add, Sub, Mul, Neg, Div, Rem, BitAnd, BitOr, BitXor, Shl, Shr, Not};
|
||||
use std::u16;
|
||||
use std::vec::Vec;
|
||||
use geom::point::Point2D;
|
||||
use util::geometry::Au;
|
||||
use util::range::{mod, Range, RangeIndex, EachIndex};
|
||||
use util::vec::*;
|
||||
|
||||
/// GlyphEntry is a port of Gecko's CompressedGlyph scheme for storing glyph data compactly.
|
||||
///
|
||||
|
@ -256,7 +254,7 @@ impl GlyphEntry {
|
|||
#[derive(Clone, Show, Copy)]
|
||||
struct DetailedGlyph {
|
||||
id: GlyphId,
|
||||
// glyph's advance, in the text's direction (RTL or RTL)
|
||||
// glyph's advance, in the text's direction (LTR or RTL)
|
||||
advance: Au,
|
||||
// glyph's offset from the font's em-box (from top-left)
|
||||
offset: Point2D<Au>,
|
||||
|
@ -296,6 +294,7 @@ impl Ord for DetailedGlyphRecord {
|
|||
// until a lookup is actually performed; this matches the expected
|
||||
// usage pattern of setting/appending all the detailed glyphs, and
|
||||
// then querying without setting.
|
||||
#[derive(Clone)]
|
||||
struct DetailedGlyphStore {
|
||||
// TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
|
||||
// optimization.
|
||||
|
@ -424,6 +423,7 @@ pub struct GlyphData {
|
|||
}
|
||||
|
||||
impl GlyphData {
|
||||
/// Creates a new entry for one glyph.
|
||||
pub fn new(id: GlyphId,
|
||||
advance: Au,
|
||||
offset: Option<Point2D<Au>>,
|
||||
|
@ -501,6 +501,7 @@ impl<'a> GlyphInfo<'a> {
|
|||
/// | +---+---+ |
|
||||
/// +---------------------------------------------+
|
||||
/// ~~~
|
||||
#[derive(Clone)]
|
||||
pub struct GlyphStore {
|
||||
// TODO(pcwalton): Allocation of this buffer is expensive. Consider a small-vector
|
||||
// optimization.
|
||||
|
@ -547,7 +548,12 @@ impl<'a> GlyphStore {
|
|||
self.detail_store.ensure_sorted();
|
||||
}
|
||||
|
||||
pub fn add_glyph_for_char_index(&mut self, i: CharIndex, data: &GlyphData) {
|
||||
/// Adds a single glyph. If `character` is present, this represents a single character;
|
||||
/// otherwise, this glyph represents multiple characters.
|
||||
pub fn add_glyph_for_char_index(&mut self,
|
||||
i: CharIndex,
|
||||
character: Option<char>,
|
||||
data: &GlyphData) {
|
||||
fn glyph_is_compressible(data: &GlyphData) -> bool {
|
||||
is_simple_glyph_id(data.id)
|
||||
&& is_simple_advance(data.advance)
|
||||
|
@ -555,10 +561,10 @@ impl<'a> GlyphStore {
|
|||
&& data.cluster_start // others are stored in detail buffer
|
||||
}
|
||||
|
||||
assert!(data.ligature_start); // can't compress ligature continuation glyphs.
|
||||
assert!(i < self.char_len());
|
||||
debug_assert!(data.ligature_start); // can't compress ligature continuation glyphs.
|
||||
debug_assert!(i < self.char_len());
|
||||
|
||||
let entry = match (data.is_missing, glyph_is_compressible(data)) {
|
||||
let mut entry = match (data.is_missing, glyph_is_compressible(data)) {
|
||||
(true, _) => GlyphEntry::missing(1),
|
||||
(false, true) => GlyphEntry::simple(data.id, data.advance),
|
||||
(false, false) => {
|
||||
|
@ -566,7 +572,14 @@ impl<'a> GlyphStore {
|
|||
self.detail_store.add_detailed_glyphs_for_entry(i, glyph);
|
||||
GlyphEntry::complex(data.cluster_start, data.ligature_start, 1)
|
||||
}
|
||||
}.adapt_character_flags_of_entry(self.entry_buffer[i.to_uint()]);
|
||||
};
|
||||
|
||||
// FIXME(pcwalton): Is this necessary? I think it's a no-op.
|
||||
entry = entry.adapt_character_flags_of_entry(self.entry_buffer[i.to_uint()]);
|
||||
|
||||
if character == Some(' ') {
|
||||
entry = entry.set_char_is_space()
|
||||
}
|
||||
|
||||
self.entry_buffer[i.to_uint()] = entry;
|
||||
}
|
||||
|
@ -691,13 +704,43 @@ impl<'a> GlyphStore {
|
|||
let entry = self.entry_buffer[i.to_uint()];
|
||||
self.entry_buffer[i.to_uint()] = entry.set_can_break_before(t);
|
||||
}
|
||||
|
||||
pub fn space_count_in_range(&self, range: &Range<CharIndex>) -> u32 {
|
||||
let mut spaces = 0;
|
||||
for index in range.each_index() {
|
||||
if self.char_is_space(index) {
|
||||
spaces += 1
|
||||
}
|
||||
}
|
||||
spaces
|
||||
}
|
||||
|
||||
pub fn distribute_extra_space_in_range(&mut self, range: &Range<CharIndex>, space: f64) {
|
||||
debug_assert!(space >= 0.0);
|
||||
if range.is_empty() {
|
||||
return
|
||||
}
|
||||
for index in range.each_index() {
|
||||
// TODO(pcwalton): Handle spaces that are detailed glyphs -- these are uncommon but
|
||||
// possible.
|
||||
let mut entry = &mut self.entry_buffer[index.to_uint()];
|
||||
if entry.is_simple() && entry.char_is_space() {
|
||||
// FIXME(pcwalton): This can overflow for very large font-sizes.
|
||||
let advance =
|
||||
((entry.value & GLYPH_ADVANCE_MASK) >> (GLYPH_ADVANCE_SHIFT as uint)) +
|
||||
Au::from_frac_px(space).to_u32().unwrap();
|
||||
entry.value = (entry.value & !GLYPH_ADVANCE_MASK) |
|
||||
(advance << (GLYPH_ADVANCE_SHIFT as uint));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the glyphs in a character range in a `GlyphStore`.
|
||||
pub struct GlyphIterator<'a> {
|
||||
store: &'a GlyphStore,
|
||||
char_index: CharIndex,
|
||||
char_range: EachIndex<int, CharIndex>,
|
||||
store: &'a GlyphStore,
|
||||
char_index: CharIndex,
|
||||
char_range: EachIndex<int, CharIndex>,
|
||||
glyph_range: Option<EachIndex<int, CharIndex>>,
|
||||
}
|
||||
|
||||
|
|
|
@ -464,7 +464,7 @@ impl Shaper {
|
|||
false,
|
||||
true,
|
||||
true);
|
||||
glyphs.add_glyph_for_char_index(char_idx, &data);
|
||||
glyphs.add_glyph_for_char_index(char_idx, Some(character), &data);
|
||||
} else {
|
||||
// collect all glyphs to be assigned to the first character.
|
||||
let mut datas = vec!();
|
||||
|
|
|
@ -120,7 +120,7 @@ impl<'a> Iterator for CharacterSliceIterator<'a> {
|
|||
|
||||
debug_assert!(!self.range.is_empty());
|
||||
let index_to_return = self.range.begin();
|
||||
self.range.adjust_by(CharIndex(1), CharIndex(0));
|
||||
self.range.adjust_by(CharIndex(1), CharIndex(-1));
|
||||
if self.range.is_empty() {
|
||||
// We're done.
|
||||
self.glyph_run = None
|
||||
|
@ -297,7 +297,7 @@ impl<'a> TextRun {
|
|||
|
||||
pub fn advance_for_range(&self, range: &Range<CharIndex>) -> Au {
|
||||
// TODO(Issue #199): alter advance direction for RTL
|
||||
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
|
||||
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
|
||||
self.natural_word_slices_in_range(range)
|
||||
.fold(Au(0), |advance, slice| {
|
||||
advance + slice.glyphs.advance_for_char_range(&slice.range)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue