Format gfx text #21373

This commit is contained in:
kingdido999 2018-09-07 10:49:07 +08:00
parent 41a6c0cc39
commit aa4a8eb88d
6 changed files with 420 additions and 266 deletions

View file

@ -4,7 +4,10 @@
use app_units::Au; use app_units::Au;
use euclid::Point2D; use euclid::Point2D;
#[cfg(all(feature = "unstable", any(target_feature = "sse2", target_feature = "neon")))] #[cfg(all(
feature = "unstable",
any(target_feature = "sse2", target_feature = "neon")
))]
use packed_simd::u32x4; use packed_simd::u32x4;
use range::{self, EachIndex, Range, RangeIndex}; use range::{self, EachIndex, Range, RangeIndex};
use std::{fmt, mem, u16}; use std::{fmt, mem, u16};
@ -28,9 +31,7 @@ pub struct GlyphEntry {
impl GlyphEntry { impl GlyphEntry {
fn new(value: u32) -> GlyphEntry { fn new(value: u32) -> GlyphEntry {
GlyphEntry { GlyphEntry { value: value }
value: value,
}
} }
fn initial() -> GlyphEntry { fn initial() -> GlyphEntry {
@ -54,11 +55,11 @@ impl GlyphEntry {
fn complex(starts_cluster: bool, starts_ligature: bool, glyph_count: usize) -> GlyphEntry { fn complex(starts_cluster: bool, starts_ligature: bool, glyph_count: usize) -> GlyphEntry {
assert!(glyph_count <= u16::MAX as usize); assert!(glyph_count <= u16::MAX as usize);
debug!("creating complex glyph entry: starts_cluster={}, starts_ligature={}, \ debug!(
glyph_count={}", "creating complex glyph entry: starts_cluster={}, starts_ligature={}, \
starts_cluster, glyph_count={}",
starts_ligature, starts_cluster, starts_ligature, glyph_count
glyph_count); );
GlyphEntry::new(glyph_count as u32) GlyphEntry::new(glyph_count as u32)
} }
@ -73,16 +74,16 @@ pub type GlyphId = u32;
// TODO: make this more type-safe. // TODO: make this more type-safe.
const FLAG_CHAR_IS_SPACE: u32 = 0x40000000; const FLAG_CHAR_IS_SPACE: u32 = 0x40000000;
#[cfg(feature = "unstable")] #[cfg(feature = "unstable")]
#[cfg(any(target_feature = "sse2", target_feature = "neon"))] #[cfg(any(target_feature = "sse2", target_feature = "neon"))]
const FLAG_CHAR_IS_SPACE_SHIFT: u32 = 30; const FLAG_CHAR_IS_SPACE_SHIFT: u32 = 30;
const FLAG_IS_SIMPLE_GLYPH: u32 = 0x80000000; const FLAG_IS_SIMPLE_GLYPH: u32 = 0x80000000;
// glyph advance; in Au's. // glyph advance; in Au's.
const GLYPH_ADVANCE_MASK: u32 = 0x3FFF0000; const GLYPH_ADVANCE_MASK: u32 = 0x3FFF0000;
const GLYPH_ADVANCE_SHIFT: u32 = 16; const GLYPH_ADVANCE_SHIFT: u32 = 16;
const GLYPH_ID_MASK: u32 = 0x0000FFFF; const GLYPH_ID_MASK: u32 = 0x0000FFFF;
// Non-simple glyphs (more than one glyph per char; missing glyph, // Non-simple glyphs (more than one glyph per char; missing glyph,
// newline, tab, large advance, or nonzero x/y offsets) may have one // newline, tab, large advance, or nonzero x/y offsets) may have one
@ -91,7 +92,7 @@ const GLYPH_ID_MASK: u32 = 0x0000FFFF;
// unicode char. // unicode char.
// The number of detailed glyphs for this char. // The number of detailed glyphs for this char.
const GLYPH_COUNT_MASK: u32 = 0x0000FFFF; const GLYPH_COUNT_MASK: u32 = 0x0000FFFF;
fn is_simple_glyph_id(id: GlyphId) -> bool { fn is_simple_glyph_id(id: GlyphId) -> bool {
((id as u32) & GLYPH_ID_MASK) == id ((id as u32) & GLYPH_ID_MASK) == id
@ -205,8 +206,8 @@ struct DetailedGlyphStore {
impl<'a> DetailedGlyphStore { impl<'a> DetailedGlyphStore {
fn new() -> DetailedGlyphStore { fn new() -> DetailedGlyphStore {
DetailedGlyphStore { DetailedGlyphStore {
detail_buffer: vec!(), // TODO: default size? detail_buffer: vec![], // TODO: default size?
detail_lookup: vec!(), detail_lookup: vec![],
lookup_is_sorted: false, lookup_is_sorted: false,
} }
} }
@ -217,7 +218,10 @@ impl<'a> DetailedGlyphStore {
detail_offset: self.detail_buffer.len(), detail_offset: self.detail_buffer.len(),
}; };
debug!("Adding entry[off={:?}] for detailed glyphs: {:?}", entry_offset, glyphs); debug!(
"Adding entry[off={:?}] for detailed glyphs: {:?}",
entry_offset, glyphs
);
/* TODO: don't actually assert this until asserts are compiled /* TODO: don't actually assert this until asserts are compiled
in/out based on severity, debug/release, etc. This assertion in/out based on severity, debug/release, etc. This assertion
@ -235,9 +239,15 @@ impl<'a> DetailedGlyphStore {
self.lookup_is_sorted = false; self.lookup_is_sorted = false;
} }
fn detailed_glyphs_for_entry(&'a self, entry_offset: ByteIndex, count: u16) fn detailed_glyphs_for_entry(
-> &'a [DetailedGlyph] { &'a self,
debug!("Requesting detailed glyphs[n={}] for entry[off={:?}]", count, entry_offset); entry_offset: ByteIndex,
count: u16,
) -> &'a [DetailedGlyph] {
debug!(
"Requesting detailed glyphs[n={}] for entry[off={:?}]",
count, entry_offset
);
// FIXME: Is this right? --pcwalton // FIXME: Is this right? --pcwalton
// TODO: should fix this somewhere else // TODO: should fix this somewhere else
@ -253,18 +263,21 @@ impl<'a> DetailedGlyphStore {
detail_offset: 0, // unused detail_offset: 0, // unused
}; };
let i = self.detail_lookup.binary_search(&key) let i = self
.detail_lookup
.binary_search(&key)
.expect("Invalid index not found in detailed glyph lookup table!"); .expect("Invalid index not found in detailed glyph lookup table!");
let main_detail_offset = self.detail_lookup[i].detail_offset; let main_detail_offset = self.detail_lookup[i].detail_offset;
assert!(main_detail_offset + (count as usize) <= self.detail_buffer.len()); assert!(main_detail_offset + (count as usize) <= self.detail_buffer.len());
// return a slice into the buffer // return a slice into the buffer
&self.detail_buffer[main_detail_offset .. main_detail_offset + count as usize] &self.detail_buffer[main_detail_offset..main_detail_offset + count as usize]
} }
fn detailed_glyph_with_index(&'a self, fn detailed_glyph_with_index(
entry_offset: ByteIndex, &'a self,
detail_offset: u16) entry_offset: ByteIndex,
-> &'a DetailedGlyph { detail_offset: u16,
) -> &'a DetailedGlyph {
assert!((detail_offset as usize) <= self.detail_buffer.len()); assert!((detail_offset as usize) <= self.detail_buffer.len());
assert!(self.lookup_is_sorted); assert!(self.lookup_is_sorted);
@ -273,7 +286,9 @@ impl<'a> DetailedGlyphStore {
detail_offset: 0, // unused detail_offset: 0, // unused
}; };
let i = self.detail_lookup.binary_search(&key) let i = self
.detail_lookup
.binary_search(&key)
.expect("Invalid index not found in detailed glyph lookup table!"); .expect("Invalid index not found in detailed glyph lookup table!");
let main_detail_offset = self.detail_lookup[i].detail_offset; let main_detail_offset = self.detail_lookup[i].detail_offset;
assert!(main_detail_offset + (detail_offset as usize) < self.detail_buffer.len()); assert!(main_detail_offset + (detail_offset as usize) < self.detail_buffer.len());
@ -290,7 +305,7 @@ impl<'a> DetailedGlyphStore {
// immutable locations thus don't play well with freezing. // immutable locations thus don't play well with freezing.
// Thar be dragons here. You have been warned. (Tips accepted.) // Thar be dragons here. You have been warned. (Tips accepted.)
let mut unsorted_records: Vec<DetailedGlyphRecord> = vec!(); let mut unsorted_records: Vec<DetailedGlyphRecord> = vec![];
mem::swap(&mut self.detail_lookup, &mut unsorted_records); mem::swap(&mut self.detail_lookup, &mut unsorted_records);
let mut mut_records: Vec<DetailedGlyphRecord> = unsorted_records; let mut mut_records: Vec<DetailedGlyphRecord> = unsorted_records;
mut_records.sort_by(|a, b| { mut_records.sort_by(|a, b| {
@ -320,12 +335,13 @@ pub struct GlyphData {
impl GlyphData { impl GlyphData {
/// Creates a new entry for one glyph. /// Creates a new entry for one glyph.
pub fn new(id: GlyphId, pub fn new(
advance: Au, id: GlyphId,
offset: Option<Point2D<Au>>, advance: Au,
cluster_start: bool, offset: Option<Point2D<Au>>,
ligature_start: bool) cluster_start: bool,
-> GlyphData { ligature_start: bool,
) -> GlyphData {
GlyphData { GlyphData {
id: id, id: id,
advance: advance, advance: advance,
@ -351,8 +367,11 @@ impl<'a> GlyphInfo<'a> {
match self { match self {
GlyphInfo::Simple(store, entry_i) => store.entry_buffer[entry_i.to_usize()].id(), GlyphInfo::Simple(store, entry_i) => store.entry_buffer[entry_i.to_usize()].id(),
GlyphInfo::Detail(store, entry_i, detail_j) => { GlyphInfo::Detail(store, entry_i, detail_j) => {
store.detail_store.detailed_glyph_with_index(entry_i, detail_j).id store
} .detail_store
.detailed_glyph_with_index(entry_i, detail_j)
.id
},
} }
} }
@ -362,8 +381,11 @@ impl<'a> GlyphInfo<'a> {
match self { match self {
GlyphInfo::Simple(store, entry_i) => store.entry_buffer[entry_i.to_usize()].advance(), GlyphInfo::Simple(store, entry_i) => store.entry_buffer[entry_i.to_usize()].advance(),
GlyphInfo::Detail(store, entry_i, detail_j) => { GlyphInfo::Detail(store, entry_i, detail_j) => {
store.detail_store.detailed_glyph_with_index(entry_i, detail_j).advance store
} .detail_store
.detailed_glyph_with_index(entry_i, detail_j)
.advance
},
} }
} }
@ -371,9 +393,12 @@ impl<'a> GlyphInfo<'a> {
pub fn offset(self) -> Option<Point2D<Au>> { pub fn offset(self) -> Option<Point2D<Au>> {
match self { match self {
GlyphInfo::Simple(_, _) => None, GlyphInfo::Simple(_, _) => None,
GlyphInfo::Detail(store, entry_i, detail_j) => { GlyphInfo::Detail(store, entry_i, detail_j) => Some(
Some(store.detail_store.detailed_glyph_with_index(entry_i, detail_j).offset) store
} .detail_store
.detailed_glyph_with_index(entry_i, detail_j)
.offset,
),
} }
} }
@ -477,14 +502,11 @@ impl<'a> GlyphStore {
} }
/// Adds a single glyph. /// Adds a single glyph.
pub fn add_glyph_for_byte_index(&mut self, pub fn add_glyph_for_byte_index(&mut self, i: ByteIndex, character: char, data: &GlyphData) {
i: ByteIndex,
character: char,
data: &GlyphData) {
let glyph_is_compressible = is_simple_glyph_id(data.id) && let glyph_is_compressible = is_simple_glyph_id(data.id) &&
is_simple_advance(data.advance) && is_simple_advance(data.advance) &&
data.offset == Point2D::zero() && data.offset == Point2D::zero() &&
data.cluster_start; // others are stored in detail buffer data.cluster_start; // others are stored in detail buffer
debug_assert!(data.ligature_start); // can't compress ligature continuation glyphs. debug_assert!(data.ligature_start); // can't compress ligature continuation glyphs.
debug_assert!(i < self.len()); debug_assert!(i < self.len());
@ -512,20 +534,29 @@ impl<'a> GlyphStore {
let glyph_count = data_for_glyphs.len(); let glyph_count = data_for_glyphs.len();
let first_glyph_data = data_for_glyphs[0]; let first_glyph_data = data_for_glyphs[0];
let glyphs_vec: Vec<DetailedGlyph> = (0..glyph_count).map(|i| { let glyphs_vec: Vec<DetailedGlyph> = (0..glyph_count)
DetailedGlyph::new(data_for_glyphs[i].id, .map(|i| {
data_for_glyphs[i].advance, DetailedGlyph::new(
data_for_glyphs[i].offset) data_for_glyphs[i].id,
}).collect(); data_for_glyphs[i].advance,
data_for_glyphs[i].offset,
)
}).collect();
self.has_detailed_glyphs = true; self.has_detailed_glyphs = true;
self.detail_store.add_detailed_glyphs_for_entry(i, &glyphs_vec); self.detail_store
.add_detailed_glyphs_for_entry(i, &glyphs_vec);
let entry = GlyphEntry::complex(first_glyph_data.cluster_start, let entry = GlyphEntry::complex(
first_glyph_data.ligature_start, first_glyph_data.cluster_start,
glyph_count); first_glyph_data.ligature_start,
glyph_count,
);
debug!("Adding multiple glyphs[idx={:?}, count={}]: {:?}", i, glyph_count, entry); debug!(
"Adding multiple glyphs[idx={:?}, count={}]: {:?}",
i, glyph_count, entry
);
self.entry_buffer[i.to_usize()] = entry; self.entry_buffer[i.to_usize()] = entry;
} }
@ -540,9 +571,13 @@ impl<'a> GlyphStore {
} }
GlyphIterator { GlyphIterator {
store: self, store: self,
byte_index: if self.is_rtl { range.end() } else { range.begin() - ByteIndex(1) }, byte_index: if self.is_rtl {
byte_range: *range, range.end()
} else {
range.begin() - ByteIndex(1)
},
byte_range: *range,
glyph_range: None, glyph_range: None,
} }
} }
@ -551,7 +586,12 @@ impl<'a> GlyphStore {
// and advance of the glyph in the range at the given advance, if reached. Otherwise, returns the // and advance of the glyph in the range at the given advance, if reached. Otherwise, returns the
// the number of glyphs and the advance for the given range. // the number of glyphs and the advance for the given range.
#[inline] #[inline]
pub fn range_index_of_advance(&self, range: &Range<ByteIndex>, advance: Au, extra_word_spacing: Au) -> (usize, Au) { pub fn range_index_of_advance(
&self,
range: &Range<ByteIndex>,
advance: Au,
extra_word_spacing: Au,
) -> (usize, Au) {
let mut index = 0; let mut index = 0;
let mut current_advance = Au(0); let mut current_advance = Au(0);
for glyph in self.iter_glyphs_for_byte_range(range) { for glyph in self.iter_glyphs_for_byte_range(range) {
@ -580,7 +620,11 @@ impl<'a> GlyphStore {
} }
#[inline] #[inline]
pub fn advance_for_byte_range_slow_path(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au { pub fn advance_for_byte_range_slow_path(
&self,
range: &Range<ByteIndex>,
extra_word_spacing: Au,
) -> Au {
self.iter_glyphs_for_byte_range(range) self.iter_glyphs_for_byte_range(range)
.fold(Au(0), |advance, glyph| { .fold(Au(0), |advance, glyph| {
if glyph.char_is_space() { if glyph.char_is_space() {
@ -594,7 +638,11 @@ impl<'a> GlyphStore {
#[inline] #[inline]
#[cfg(feature = "unstable")] #[cfg(feature = "unstable")]
#[cfg(any(target_feature = "sse2", target_feature = "neon"))] #[cfg(any(target_feature = "sse2", target_feature = "neon"))]
fn advance_for_byte_range_simple_glyphs(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au { fn advance_for_byte_range_simple_glyphs(
&self,
range: &Range<ByteIndex>,
extra_word_spacing: Au,
) -> Au {
let advance_mask = u32x4::splat(GLYPH_ADVANCE_MASK); let advance_mask = u32x4::splat(GLYPH_ADVANCE_MASK);
let space_flag_mask = u32x4::splat(FLAG_CHAR_IS_SPACE); let space_flag_mask = u32x4::splat(FLAG_CHAR_IS_SPACE);
let mut simd_advance = u32x4::splat(0); let mut simd_advance = u32x4::splat(0);
@ -614,16 +662,14 @@ impl<'a> GlyphStore {
simd_spaces = simd_spaces + spaces; simd_spaces = simd_spaces + spaces;
} }
let advance = let advance = (simd_advance.extract(0) +
(simd_advance.extract(0) + simd_advance.extract(1) +
simd_advance.extract(1) + simd_advance.extract(2) +
simd_advance.extract(2) + simd_advance.extract(3)) as i32;
simd_advance.extract(3)) as i32; let spaces = (simd_spaces.extract(0) +
let spaces = simd_spaces.extract(1) +
(simd_spaces.extract(0) + simd_spaces.extract(2) +
simd_spaces.extract(1) + simd_spaces.extract(3)) as i32;
simd_spaces.extract(2) +
simd_spaces.extract(3)) as i32;
let mut leftover_advance = Au(0); let mut leftover_advance = Au(0);
let mut leftover_spaces = 0; let mut leftover_spaces = 0;
for i in leftover_entries..range.end().to_usize() { for i in leftover_entries..range.end().to_usize() {
@ -637,8 +683,15 @@ impl<'a> GlyphStore {
/// When SIMD isn't available, fallback to the slow path. /// When SIMD isn't available, fallback to the slow path.
#[inline] #[inline]
#[cfg(not(all(feature = "unstable", any(target_feature = "sse2", target_feature = "neon"))))] #[cfg(not(all(
fn advance_for_byte_range_simple_glyphs(&self, range: &Range<ByteIndex>, extra_word_spacing: Au) -> Au { feature = "unstable",
any(target_feature = "sse2", target_feature = "neon")
)))]
fn advance_for_byte_range_simple_glyphs(
&self,
range: &Range<ByteIndex>,
extra_word_spacing: Au,
) -> Au {
self.advance_for_byte_range_slow_path(range, extra_word_spacing) self.advance_for_byte_range_slow_path(range, extra_word_spacing)
} }
@ -676,23 +729,27 @@ impl fmt::Debug for GlyphStore {
let mut detailed_buffer = self.detail_store.detail_buffer.iter(); let mut detailed_buffer = self.detail_store.detail_buffer.iter();
for entry in self.entry_buffer.iter() { for entry in self.entry_buffer.iter() {
if entry.is_simple() { if entry.is_simple() {
write!(formatter, write!(
" simple id={:?} advance={:?}\n", formatter,
entry.id(), " simple id={:?} advance={:?}\n",
entry.advance())?; entry.id(),
continue entry.advance()
)?;
continue;
} }
if entry.is_initial() { if entry.is_initial() {
continue continue;
} }
write!(formatter, " complex...")?; write!(formatter, " complex...")?;
if detailed_buffer.next().is_none() { if detailed_buffer.next().is_none() {
continue continue;
} }
write!(formatter, write!(
" detailed id={:?} advance={:?}\n", formatter,
entry.id(), " detailed id={:?} advance={:?}\n",
entry.advance())?; entry.id(),
entry.advance()
)?;
} }
Ok(()) Ok(())
} }
@ -712,27 +769,37 @@ impl<'a> GlyphIterator<'a> {
fn next_glyph_range(&mut self) -> Option<GlyphInfo<'a>> { fn next_glyph_range(&mut self) -> Option<GlyphInfo<'a>> {
match self.glyph_range.as_mut().unwrap().next() { match self.glyph_range.as_mut().unwrap().next() {
Some(j) => { Some(j) => {
Some(GlyphInfo::Detail(self.store, self.byte_index, j.get() as u16 /* ??? */)) Some(GlyphInfo::Detail(
} self.store,
self.byte_index,
j.get() as u16, /* ??? */
))
},
None => { None => {
// No more glyphs for current character. Try to get another. // No more glyphs for current character. Try to get another.
self.glyph_range = None; self.glyph_range = None;
self.next() self.next()
} },
} }
} }
// Slow path when there is a complex glyph. // Slow path when there is a complex glyph.
#[inline(never)] #[inline(never)]
fn next_complex_glyph(&mut self, entry: &GlyphEntry, i: ByteIndex) -> Option<GlyphInfo<'a>> { fn next_complex_glyph(&mut self, entry: &GlyphEntry, i: ByteIndex) -> Option<GlyphInfo<'a>> {
let glyphs = self.store.detail_store.detailed_glyphs_for_entry(i, entry.glyph_count()); let glyphs = self
self.glyph_range = Some(range::each_index(ByteIndex(0), ByteIndex(glyphs.len() as isize))); .store
.detail_store
.detailed_glyphs_for_entry(i, entry.glyph_count());
self.glyph_range = Some(range::each_index(
ByteIndex(0),
ByteIndex(glyphs.len() as isize),
));
self.next() self.next()
} }
} }
impl<'a> Iterator for GlyphIterator<'a> { impl<'a> Iterator for GlyphIterator<'a> {
type Item = GlyphInfo<'a>; type Item = GlyphInfo<'a>;
// I tried to start with something simpler and apply FlatMap, but the // I tried to start with something simpler and apply FlatMap, but the
// inability to store free variables in the FlatMap struct was problematic. // inability to store free variables in the FlatMap struct was problematic.
@ -744,7 +811,7 @@ impl<'a> Iterator for GlyphIterator<'a> {
fn next(&mut self) -> Option<GlyphInfo<'a>> { fn next(&mut self) -> Option<GlyphInfo<'a>> {
// Would use 'match' here but it borrows contents in a way that interferes with mutation. // Would use 'match' here but it borrows contents in a way that interferes with mutation.
if self.glyph_range.is_some() { if self.glyph_range.is_some() {
return self.next_glyph_range() return self.next_glyph_range();
} }
// No glyph range. Look at next byte. // No glyph range. Look at next byte.
@ -755,7 +822,7 @@ impl<'a> Iterator for GlyphIterator<'a> {
}; };
let i = self.byte_index; let i = self.byte_index;
if !self.byte_range.contains(i) { if !self.byte_range.contains(i) {
return None return None;
} }
debug_assert!(i < self.store.len()); debug_assert!(i < self.store.len());
let entry = self.store.entry_buffer[i.to_usize()]; let entry = self.store.entry_buffer[i.to_usize()];

View file

@ -9,4 +9,3 @@ pub mod glyph;
pub mod shaping; pub mod shaping;
pub mod text_run; pub mod text_run;
pub mod util; pub mod util;

View file

@ -147,10 +147,11 @@ impl Drop for Shaper {
impl Shaper { impl Shaper {
pub fn new(font: *const Font) -> Shaper { pub fn new(font: *const Font) -> Shaper {
unsafe { unsafe {
let hb_face: *mut hb_face_t = let hb_face: *mut hb_face_t = hb_face_create_for_tables(
hb_face_create_for_tables(Some(font_table_func), Some(font_table_func),
font as *const c_void as *mut c_void, font as *const c_void as *mut c_void,
None); None,
);
let hb_font: *mut hb_font_t = hb_font_create(hb_face); let hb_font: *mut hb_font_t = hb_font_create(hb_face);
// Set points-per-em. if zero, performs no hinting in that direction. // Set points-per-em. if zero, performs no hinting in that direction.
@ -158,12 +159,19 @@ impl Shaper {
hb_font_set_ppem(hb_font, pt_size as c_uint, pt_size as c_uint); hb_font_set_ppem(hb_font, pt_size as c_uint, pt_size as c_uint);
// Set scaling. Note that this takes 16.16 fixed point. // Set scaling. Note that this takes 16.16 fixed point.
hb_font_set_scale(hb_font, hb_font_set_scale(
Shaper::float_to_fixed(pt_size) as c_int, hb_font,
Shaper::float_to_fixed(pt_size) as c_int); Shaper::float_to_fixed(pt_size) as c_int,
Shaper::float_to_fixed(pt_size) as c_int,
);
// configure static function callbacks. // configure static function callbacks.
hb_font_set_funcs(hb_font, HB_FONT_FUNCS.0, font as *mut Font as *mut c_void, None); hb_font_set_funcs(
hb_font,
HB_FONT_FUNCS.0,
font as *mut Font as *mut c_void,
None,
);
Shaper { Shaper {
hb_face: hb_face, hb_face: hb_face,
@ -188,22 +196,30 @@ impl ShaperMethods for Shaper {
fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) { fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
unsafe { unsafe {
let hb_buffer: *mut hb_buffer_t = hb_buffer_create(); let hb_buffer: *mut hb_buffer_t = hb_buffer_create();
hb_buffer_set_direction(hb_buffer, if options.flags.contains(ShapingFlags::RTL_FLAG) { hb_buffer_set_direction(
HB_DIRECTION_RTL hb_buffer,
} else { if options.flags.contains(ShapingFlags::RTL_FLAG) {
HB_DIRECTION_LTR HB_DIRECTION_RTL
}); } else {
HB_DIRECTION_LTR
},
);
hb_buffer_set_script(hb_buffer, options.script.to_hb_script()); hb_buffer_set_script(hb_buffer, options.script.to_hb_script());
hb_buffer_add_utf8(hb_buffer, hb_buffer_add_utf8(
text.as_ptr() as *const c_char, hb_buffer,
text.len() as c_int, text.as_ptr() as *const c_char,
0, text.len() as c_int,
text.len() as c_int); 0,
text.len() as c_int,
);
let mut features = Vec::new(); let mut features = Vec::new();
if options.flags.contains(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG) { if options
.flags
.contains(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG)
{
features.push(hb_feature_t { features.push(hb_feature_t {
tag: LIGA, tag: LIGA,
value: 0, value: 0,
@ -211,7 +227,10 @@ impl ShaperMethods for Shaper {
end: hb_buffer_get_length(hb_buffer), end: hb_buffer_get_length(hb_buffer),
}) })
} }
if options.flags.contains(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG) { if options
.flags
.contains(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
{
features.push(hb_feature_t { features.push(hb_feature_t {
tag: KERN, tag: KERN,
value: 0, value: 0,
@ -220,7 +239,12 @@ impl ShaperMethods for Shaper {
}) })
} }
hb_shape(self.hb_font, hb_buffer, features.as_mut_ptr(), features.len() as u32); hb_shape(
self.hb_font,
hb_buffer,
features.as_mut_ptr(),
features.len() as u32,
);
self.save_glyph_results(text, options, glyphs, hb_buffer); self.save_glyph_results(text, options, glyphs, hb_buffer);
hb_buffer_destroy(hb_buffer); hb_buffer_destroy(hb_buffer);
} }
@ -228,18 +252,21 @@ impl ShaperMethods for Shaper {
} }
impl Shaper { impl Shaper {
fn save_glyph_results(&self, fn save_glyph_results(
text: &str, &self,
options: &ShapingOptions, text: &str,
glyphs: &mut GlyphStore, options: &ShapingOptions,
buffer: *mut hb_buffer_t) { glyphs: &mut GlyphStore,
buffer: *mut hb_buffer_t,
) {
let glyph_data = ShapedGlyphData::new(buffer); let glyph_data = ShapedGlyphData::new(buffer);
let glyph_count = glyph_data.len(); let glyph_count = glyph_data.len();
let byte_max = text.len(); let byte_max = text.len();
debug!("Shaped text[byte count={}], got back {} glyph info records.", debug!(
byte_max, "Shaped text[byte count={}], got back {} glyph info records.",
glyph_count); byte_max, glyph_count
);
// make map of what chars have glyphs // make map of what chars have glyphs
let mut byte_to_glyph = vec![NO_GLYPH; byte_max]; let mut byte_to_glyph = vec![NO_GLYPH; byte_max];
@ -250,9 +277,10 @@ impl Shaper {
if loc < byte_max { if loc < byte_max {
byte_to_glyph[loc] = i as i32; byte_to_glyph[loc] = i as i32;
} else { } else {
debug!("ERROR: tried to set out of range byte_to_glyph: idx={}, glyph idx={}", debug!(
loc, "ERROR: tried to set out of range byte_to_glyph: idx={}, glyph idx={}",
i); loc, i
);
} }
debug!("{} -> {}", i, loc); debug!("{} -> {}", i, loc);
} }
@ -296,10 +324,14 @@ impl Shaper {
} }
// if there's just one glyph, then we don't need further checks. // if there's just one glyph, then we don't need further checks.
if glyph_span.len() == 1 { break; } if glyph_span.len() == 1 {
break;
}
// if no glyphs were found yet, extend the char byte range more. // if no glyphs were found yet, extend the char byte range more.
if glyph_span.len() == 0 { continue; } if glyph_span.len() == 0 {
continue;
}
// If byte_range now includes all the byte offsets found in glyph_span, then we // If byte_range now includes all the byte offsets found in glyph_span, then we
// have found a contiguous "cluster" and can stop extending it. // have found a contiguous "cluster" and can stop extending it.
@ -308,11 +340,11 @@ impl Shaper {
let loc = glyph_data.byte_offset_of_glyph(j) as usize; let loc = glyph_data.byte_offset_of_glyph(j) as usize;
if !(byte_range.start <= loc && loc < byte_range.end) { if !(byte_range.start <= loc && loc < byte_range.end) {
all_glyphs_are_within_cluster = false; all_glyphs_are_within_cluster = false;
break break;
} }
} }
if all_glyphs_are_within_cluster { if all_glyphs_are_within_cluster {
break break;
} }
// Otherwise, the bytes we have seen so far correspond to a non-contiguous set of // Otherwise, the bytes we have seen so far correspond to a non-contiguous set of
@ -348,34 +380,29 @@ impl Shaper {
const TAB_COLS: i32 = 8; const TAB_COLS: i32 = 8;
let (space_glyph_id, space_advance) = glyph_space_advance(self.font); let (space_glyph_id, space_advance) = glyph_space_advance(self.font);
let advance = Au::from_f64_px(space_advance) * TAB_COLS; let advance = Au::from_f64_px(space_advance) * TAB_COLS;
let data = GlyphData::new(space_glyph_id, let data =
advance, GlyphData::new(space_glyph_id, advance, Default::default(), true, true);
Default::default(),
true,
true);
glyphs.add_glyph_for_byte_index(byte_idx, character, &data); glyphs.add_glyph_for_byte_index(byte_idx, character, &data);
} else { } else {
let shape = glyph_data.entry_for_glyph(glyph_span.start, &mut y_pos); let shape = glyph_data.entry_for_glyph(glyph_span.start, &mut y_pos);
let advance = self.advance_for_shaped_glyph(shape.advance, character, options); let advance = self.advance_for_shaped_glyph(shape.advance, character, options);
let data = GlyphData::new(shape.codepoint, let data = GlyphData::new(shape.codepoint, advance, shape.offset, true, true);
advance,
shape.offset,
true,
true);
glyphs.add_glyph_for_byte_index(byte_idx, character, &data); glyphs.add_glyph_for_byte_index(byte_idx, character, &data);
} }
} else { } else {
// collect all glyphs to be assigned to the first character. // collect all glyphs to be assigned to the first character.
let mut datas = vec!(); let mut datas = vec![];
for glyph_i in glyph_span.clone() { for glyph_i in glyph_span.clone() {
let shape = glyph_data.entry_for_glyph(glyph_i, &mut y_pos); let shape = glyph_data.entry_for_glyph(glyph_i, &mut y_pos);
datas.push(GlyphData::new(shape.codepoint, datas.push(GlyphData::new(
shape.advance, shape.codepoint,
shape.offset, shape.advance,
true, // treat as cluster start shape.offset,
glyph_i > glyph_span.start)); true, // treat as cluster start
// all but first are ligature continuations glyph_i > glyph_span.start,
));
// all but first are ligature continuations
} }
// now add the detailed glyph entry. // now add the detailed glyph entry.
glyphs.add_glyphs_for_byte_index(byte_idx, &datas); glyphs.add_glyphs_for_byte_index(byte_idx, &datas);
@ -390,8 +417,12 @@ impl Shaper {
glyphs.finalize_changes(); glyphs.finalize_changes();
} }
fn advance_for_shaped_glyph(&self, mut advance: Au, character: char, options: &ShapingOptions) fn advance_for_shaped_glyph(
-> Au { &self,
mut advance: Au,
character: char,
options: &ShapingOptions,
) -> Au {
if let Some(letter_spacing) = options.letter_spacing { if let Some(letter_spacing) = options.letter_spacing {
advance = advance + letter_spacing; advance = advance + letter_spacing;
}; };
@ -403,7 +434,8 @@ impl Shaper {
if character == ' ' || character == '\u{a0}' { if character == ' ' || character == '\u{a0}' {
// https://drafts.csswg.org/css-text-3/#word-spacing-property // https://drafts.csswg.org/css-text-3/#word-spacing-property
let (length, percent) = options.word_spacing; let (length, percent) = options.word_spacing;
advance = (advance + length) + Au::new((advance.0 as f32 * percent.into_inner()) as i32); advance =
(advance + length) + Au::new((advance.0 as f32 * percent.into_inner()) as i32);
} }
advance advance
@ -420,20 +452,29 @@ lazy_static! {
let hb_funcs = hb_font_funcs_create(); let hb_funcs = hb_font_funcs_create();
hb_font_funcs_set_nominal_glyph_func(hb_funcs, Some(glyph_func), ptr::null_mut(), None); hb_font_funcs_set_nominal_glyph_func(hb_funcs, Some(glyph_func), ptr::null_mut(), None);
hb_font_funcs_set_glyph_h_advance_func( hb_font_funcs_set_glyph_h_advance_func(
hb_funcs, Some(glyph_h_advance_func), ptr::null_mut(), None); hb_funcs,
Some(glyph_h_advance_func),
ptr::null_mut(),
None,
);
hb_font_funcs_set_glyph_h_kerning_func( hb_font_funcs_set_glyph_h_kerning_func(
hb_funcs, Some(glyph_h_kerning_func), ptr::null_mut(), None); hb_funcs,
Some(glyph_h_kerning_func),
ptr::null_mut(),
None,
);
FontFuncs(hb_funcs) FontFuncs(hb_funcs)
}; };
} }
extern fn glyph_func(_: *mut hb_font_t, extern "C" fn glyph_func(
font_data: *mut c_void, _: *mut hb_font_t,
unicode: hb_codepoint_t, font_data: *mut c_void,
glyph: *mut hb_codepoint_t, unicode: hb_codepoint_t,
_: *mut c_void) glyph: *mut hb_codepoint_t,
-> hb_bool_t { _: *mut c_void,
) -> hb_bool_t {
let font: *const Font = font_data as *const Font; let font: *const Font = font_data as *const Font;
assert!(!font.is_null()); assert!(!font.is_null());
@ -442,17 +483,18 @@ extern fn glyph_func(_: *mut hb_font_t,
Some(g) => { Some(g) => {
*glyph = g as hb_codepoint_t; *glyph = g as hb_codepoint_t;
true as hb_bool_t true as hb_bool_t
} },
None => false as hb_bool_t None => false as hb_bool_t,
} }
} }
} }
extern fn glyph_h_advance_func(_: *mut hb_font_t, extern "C" fn glyph_h_advance_func(
font_data: *mut c_void, _: *mut hb_font_t,
glyph: hb_codepoint_t, font_data: *mut c_void,
_: *mut c_void) glyph: hb_codepoint_t,
-> hb_position_t { _: *mut c_void,
) -> hb_position_t {
let font: *mut Font = font_data as *mut Font; let font: *mut Font = font_data as *mut Font;
assert!(!font.is_null()); assert!(!font.is_null());
@ -468,19 +510,20 @@ fn glyph_space_advance(font: *const Font) -> (hb_codepoint_t, f64) {
match unsafe { (*font).glyph_index(space_unicode) } { match unsafe { (*font).glyph_index(space_unicode) } {
Some(g) => { Some(g) => {
space_glyph = g as hb_codepoint_t; space_glyph = g as hb_codepoint_t;
} },
None => panic!("No space info") None => panic!("No space info"),
} }
let space_advance = unsafe { (*font).glyph_h_advance(space_glyph as GlyphId) }; let space_advance = unsafe { (*font).glyph_h_advance(space_glyph as GlyphId) };
(space_glyph, space_advance) (space_glyph, space_advance)
} }
extern fn glyph_h_kerning_func(_: *mut hb_font_t, extern "C" fn glyph_h_kerning_func(
font_data: *mut c_void, _: *mut hb_font_t,
first_glyph: hb_codepoint_t, font_data: *mut c_void,
second_glyph: hb_codepoint_t, first_glyph: hb_codepoint_t,
_: *mut c_void) second_glyph: hb_codepoint_t,
-> hb_position_t { _: *mut c_void,
) -> hb_position_t {
let font: *mut Font = font_data as *mut Font; let font: *mut Font = font_data as *mut Font;
assert!(!font.is_null()); assert!(!font.is_null());
@ -491,10 +534,11 @@ extern fn glyph_h_kerning_func(_: *mut hb_font_t,
} }
// Callback to get a font table out of a font. // Callback to get a font table out of a font.
extern fn font_table_func(_: *mut hb_face_t, extern "C" fn font_table_func(
tag: hb_tag_t, _: *mut hb_face_t,
user_data: *mut c_void) tag: hb_tag_t,
-> *mut hb_blob_t { user_data: *mut c_void,
) -> *mut hb_blob_t {
unsafe { unsafe {
// NB: These asserts have security implications. // NB: These asserts have security implications.
let font = user_data as *const Font; let font = user_data as *const Font;
@ -511,20 +555,22 @@ extern fn font_table_func(_: *mut hb_face_t,
let buf = (*font_table_ptr).buffer(); let buf = (*font_table_ptr).buffer();
// HarfBuzz calls `destroy_blob_func` when the buffer is no longer needed. // HarfBuzz calls `destroy_blob_func` when the buffer is no longer needed.
let blob = hb_blob_create(buf.as_ptr() as *const c_char, let blob = hb_blob_create(
buf.len() as c_uint, buf.as_ptr() as *const c_char,
HB_MEMORY_MODE_READONLY, buf.len() as c_uint,
font_table_ptr as *mut c_void, HB_MEMORY_MODE_READONLY,
Some(destroy_blob_func)); font_table_ptr as *mut c_void,
Some(destroy_blob_func),
);
assert!(!blob.is_null()); assert!(!blob.is_null());
blob blob
} },
} }
} }
} }
extern fn destroy_blob_func(font_table_ptr: *mut c_void) { extern "C" fn destroy_blob_func(font_table_ptr: *mut c_void) {
unsafe { unsafe {
drop(Box::from_raw(font_table_ptr as *mut FontTable)); drop(Box::from_raw(font_table_ptr as *mut FontTable));
} }

View file

@ -17,4 +17,3 @@ pub mod harfbuzz;
pub trait ShaperMethods { pub trait ShaperMethods {
fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore); fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore);
} }

View file

@ -155,7 +155,7 @@ impl<'a> Iterator for CharacterSliceIterator<'a> {
let byte_start = self.range.begin(); let byte_start = self.range.begin();
let byte_len = match self.text[byte_start.to_usize()..].chars().next() { let byte_len = match self.text[byte_start.to_usize()..].chars().next() {
Some(ch) => ByteIndex(ch.len_utf8() as isize), Some(ch) => ByteIndex(ch.len_utf8() as isize),
None => unreachable!() // XXX refactor? None => unreachable!(), // XXX refactor?
}; };
self.range.adjust_by(byte_len, -byte_len); self.range.adjust_by(byte_len, -byte_len);
@ -178,24 +178,36 @@ impl<'a> Iterator for CharacterSliceIterator<'a> {
impl<'a> TextRun { impl<'a> TextRun {
/// Constructs a new text run. Also returns if there is a line break at the beginning /// Constructs a new text run. Also returns if there is a line break at the beginning
pub fn new(font: &mut Font, text: String, options: &ShapingOptions, pub fn new(
bidi_level: bidi::Level, breaker: &mut Option<LineBreakLeafIter>) -> (TextRun, bool) { font: &mut Font,
text: String,
options: &ShapingOptions,
bidi_level: bidi::Level,
breaker: &mut Option<LineBreakLeafIter>,
) -> (TextRun, bool) {
let (glyphs, break_at_zero) = TextRun::break_and_shape(font, &text, options, breaker); let (glyphs, break_at_zero) = TextRun::break_and_shape(font, &text, options, breaker);
(TextRun { (
text: Arc::new(text), TextRun {
font_metrics: font.metrics.clone(), text: Arc::new(text),
font_template: font.handle.template(), font_metrics: font.metrics.clone(),
font_key: font.font_key, font_template: font.handle.template(),
actual_pt_size: font.actual_pt_size, font_key: font.font_key,
glyphs: Arc::new(glyphs), actual_pt_size: font.actual_pt_size,
bidi_level: bidi_level, glyphs: Arc::new(glyphs),
extra_word_spacing: Au(0), bidi_level: bidi_level,
}, break_at_zero) extra_word_spacing: Au(0),
},
break_at_zero,
)
} }
pub fn break_and_shape(font: &mut Font, text: &str, options: &ShapingOptions, pub fn break_and_shape(
breaker: &mut Option<LineBreakLeafIter>) -> (Vec<GlyphRun>, bool) { font: &mut Font,
let mut glyphs = vec!(); text: &str,
options: &ShapingOptions,
breaker: &mut Option<LineBreakLeafIter>,
) -> (Vec<GlyphRun>, bool) {
let mut glyphs = vec![];
let mut slice = 0..0; let mut slice = 0..0;
let mut finished = false; let mut finished = false;
@ -203,7 +215,7 @@ impl<'a> TextRun {
if breaker.is_none() { if breaker.is_none() {
if text.len() == 0 { if text.len() == 0 {
return (glyphs, true) return (glyphs, true);
} }
*breaker = Some(LineBreakLeafIter::new(&text, 0)); *breaker = Some(LineBreakLeafIter::new(&text, 0));
} }
@ -225,29 +237,39 @@ impl<'a> TextRun {
// Split off any trailing whitespace into a separate glyph run. // Split off any trailing whitespace into a separate glyph run.
let mut whitespace = slice.end..slice.end; let mut whitespace = slice.end..slice.end;
if let Some((i, _)) = word.char_indices().rev() if let Some((i, _)) = word
.take_while(|&(_, c)| char_is_whitespace(c)).last() { .char_indices()
whitespace.start = slice.start + i; .rev()
slice.end = whitespace.start; .take_while(|&(_, c)| char_is_whitespace(c))
} else if idx != text.len() && options.flags.contains(ShapingFlags::KEEP_ALL_FLAG) { .last()
// If there's no whitespace and word-break is set to {
// keep-all, try increasing the slice. whitespace.start = slice.start + i;
continue; slice.end = whitespace.start;
} } else if idx != text.len() && options.flags.contains(ShapingFlags::KEEP_ALL_FLAG) {
// If there's no whitespace and word-break is set to
// keep-all, try increasing the slice.
continue;
}
if slice.len() > 0 { if slice.len() > 0 {
glyphs.push(GlyphRun { glyphs.push(GlyphRun {
glyph_store: font.shape_text(&text[slice.clone()], options), glyph_store: font.shape_text(&text[slice.clone()], options),
range: Range::new(ByteIndex(slice.start as isize), range: Range::new(
ByteIndex(slice.len() as isize)), ByteIndex(slice.start as isize),
ByteIndex(slice.len() as isize),
),
}); });
} }
if whitespace.len() > 0 { if whitespace.len() > 0 {
let mut options = options.clone(); let mut options = options.clone();
options.flags.insert(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG); options
.flags
.insert(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG);
glyphs.push(GlyphRun { glyphs.push(GlyphRun {
glyph_store: font.shape_text(&text[whitespace.clone()], &options), glyph_store: font.shape_text(&text[whitespace.clone()], &options),
range: Range::new(ByteIndex(whitespace.start as isize), range: Range::new(
ByteIndex(whitespace.len() as isize)), ByteIndex(whitespace.start as isize),
ByteIndex(whitespace.len() as isize),
),
}); });
} }
slice.start = whitespace.end; slice.start = whitespace.end;
@ -265,36 +287,46 @@ impl<'a> TextRun {
pub fn advance_for_range(&self, range: &Range<ByteIndex>) -> Au { pub fn advance_for_range(&self, range: &Range<ByteIndex>) -> Au {
if range.is_empty() { if range.is_empty() {
return Au(0) return Au(0);
} }
// TODO(Issue #199): alter advance direction for RTL // 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) self.natural_word_slices_in_range(range)
.fold(Au(0), |advance, slice| { .fold(Au(0), |advance, slice| {
advance + slice.glyphs.advance_for_byte_range(&slice.range, self.extra_word_spacing) advance + slice
.glyphs
.advance_for_byte_range(&slice.range, self.extra_word_spacing)
}) })
} }
pub fn metrics_for_range(&self, range: &Range<ByteIndex>) -> RunMetrics { pub fn metrics_for_range(&self, range: &Range<ByteIndex>) -> RunMetrics {
RunMetrics::new(self.advance_for_range(range), RunMetrics::new(
self.font_metrics.ascent, self.advance_for_range(range),
self.font_metrics.descent) self.font_metrics.ascent,
self.font_metrics.descent,
)
} }
pub fn metrics_for_slice(&self, glyphs: &GlyphStore, slice_range: &Range<ByteIndex>) pub fn metrics_for_slice(
-> RunMetrics { &self,
RunMetrics::new(glyphs.advance_for_byte_range(slice_range, self.extra_word_spacing), glyphs: &GlyphStore,
self.font_metrics.ascent, slice_range: &Range<ByteIndex>,
self.font_metrics.descent) ) -> RunMetrics {
RunMetrics::new(
glyphs.advance_for_byte_range(slice_range, self.extra_word_spacing),
self.font_metrics.ascent,
self.font_metrics.descent,
)
} }
pub fn min_width_for_range(&self, range: &Range<ByteIndex>) -> Au { pub fn min_width_for_range(&self, range: &Range<ByteIndex>) -> Au {
debug!("iterating outer range {:?}", range); debug!("iterating outer range {:?}", range);
self.natural_word_slices_in_range(range).fold(Au(0), |max_piece_width, slice| { self.natural_word_slices_in_range(range)
debug!("iterated on {:?}[{:?}]", slice.offset, slice.range); .fold(Au(0), |max_piece_width, slice| {
max(max_piece_width, self.advance_for_range(&slice.range)) debug!("iterated on {:?}[{:?}]", slice.offset, slice.range);
}) max(max_piece_width, self.advance_for_range(&slice.range))
})
} }
pub fn minimum_splittable_inline_size(&self, range: &Range<ByteIndex>) -> Au { pub fn minimum_splittable_inline_size(&self, range: &Range<ByteIndex>) -> Au {
@ -309,13 +341,15 @@ impl<'a> TextRun {
let self_ptr = self as *const TextRun; let self_ptr = self as *const TextRun;
INDEX_OF_FIRST_GLYPH_RUN_CACHE.with(|index_of_first_glyph_run_cache| { INDEX_OF_FIRST_GLYPH_RUN_CACHE.with(|index_of_first_glyph_run_cache| {
if let Some((last_text_run, last_index, last_result)) = if let Some((last_text_run, last_index, last_result)) =
index_of_first_glyph_run_cache.get() { index_of_first_glyph_run_cache.get()
{
if last_text_run == self_ptr && last_index == index { if last_text_run == self_ptr && last_index == index {
return Some(last_result) return Some(last_result);
} }
} }
if let Ok(result) = (&**self.glyphs).binary_search_by(|current| current.compare(&index)) { if let Ok(result) = (&**self.glyphs).binary_search_by(|current| current.compare(&index))
{
index_of_first_glyph_run_cache.set(Some((self_ptr, index, result))); index_of_first_glyph_run_cache.set(Some((self_ptr, index, result)));
Some(result) Some(result)
} else { } else {
@ -339,18 +373,22 @@ impl<'a> TextRun {
let mut remaining = advance; let mut remaining = advance;
self.natural_word_slices_in_range(range) self.natural_word_slices_in_range(range)
.map(|slice| { .map(|slice| {
let (slice_index, slice_advance) = let (slice_index, slice_advance) = slice.glyphs.range_index_of_advance(
slice.glyphs.range_index_of_advance(&slice.range, remaining, self.extra_word_spacing); &slice.range,
remaining,
self.extra_word_spacing,
);
remaining -= slice_advance; remaining -= slice_advance;
slice_index slice_index
}) }).sum()
.sum()
} }
/// Returns an iterator that will iterate over all slices of glyphs that represent natural /// Returns an iterator that will iterate over all slices of glyphs that represent natural
/// words in the given range. /// words in the given range.
pub fn natural_word_slices_in_range(&'a self, range: &Range<ByteIndex>) pub fn natural_word_slices_in_range(
-> NaturalWordSliceIterator<'a> { &'a self,
range: &Range<ByteIndex>,
) -> NaturalWordSliceIterator<'a> {
let index = match self.index_of_first_glyph_run_containing(range.begin()) { let index = match self.index_of_first_glyph_run_containing(range.begin()) {
None => self.glyphs.len(), None => self.glyphs.len(),
Some(index) => index, Some(index) => index,
@ -365,20 +403,22 @@ impl<'a> TextRun {
/// Returns an iterator that over natural word slices in visual order (left to right or /// Returns an iterator that over natural word slices in visual order (left to right or
/// right to left, depending on the bidirectional embedding level). /// right to left, depending on the bidirectional embedding level).
pub fn natural_word_slices_in_visual_order(&'a self, range: &Range<ByteIndex>) pub fn natural_word_slices_in_visual_order(
-> NaturalWordSliceIterator<'a> { &'a self,
range: &Range<ByteIndex>,
) -> NaturalWordSliceIterator<'a> {
// Iterate in reverse order if bidi level is RTL. // Iterate in reverse order if bidi level is RTL.
let reverse = self.bidi_level.is_rtl(); let reverse = self.bidi_level.is_rtl();
let index = if reverse { let index = if reverse {
match self.index_of_first_glyph_run_containing(range.end() - ByteIndex(1)) { match self.index_of_first_glyph_run_containing(range.end() - ByteIndex(1)) {
Some(i) => i + 1, // In reverse mode, index points one past the next element. Some(i) => i + 1, // In reverse mode, index points one past the next element.
None => 0 None => 0,
} }
} else { } else {
match self.index_of_first_glyph_run_containing(range.begin()) { match self.index_of_first_glyph_run_containing(range.begin()) {
Some(i) => i, Some(i) => i,
None => self.glyphs.len() None => self.glyphs.len(),
} }
}; };
NaturalWordSliceIterator { NaturalWordSliceIterator {
@ -391,8 +431,10 @@ impl<'a> TextRun {
/// Returns an iterator that will iterate over all slices of glyphs that represent individual /// Returns an iterator that will iterate over all slices of glyphs that represent individual
/// characters in the given range. /// characters in the given range.
pub fn character_slices_in_range(&'a self, range: &Range<ByteIndex>) pub fn character_slices_in_range(
-> CharacterSliceIterator<'a> { &'a self,
range: &Range<ByteIndex>,
) -> CharacterSliceIterator<'a> {
let index = match self.index_of_first_glyph_run_containing(range.begin()) { let index = match self.index_of_first_glyph_run_containing(range.begin()) {
None => self.glyphs.len(), None => self.glyphs.len(),
Some(index) => index, Some(index) => index,

View file

@ -9,7 +9,7 @@ pub enum CompressionMode {
CompressNone, CompressNone,
CompressWhitespace, CompressWhitespace,
CompressWhitespaceNewline, CompressWhitespaceNewline,
DiscardNewline DiscardNewline,
} }
// ported from Gecko's nsTextFrameUtils::TransformText. // ported from Gecko's nsTextFrameUtils::TransformText.
@ -22,11 +22,12 @@ pub enum CompressionMode {
// * Issue #114: record skipped and kept chars for mapping original to new text // * Issue #114: record skipped and kept chars for mapping original to new text
// //
// * Untracked: various edge cases for bidi, CJK, etc. // * Untracked: various edge cases for bidi, CJK, etc.
pub fn transform_text(text: &str, pub fn transform_text(
mode: CompressionMode, text: &str,
incoming_whitespace: bool, mode: CompressionMode,
output_text: &mut String) incoming_whitespace: bool,
-> bool { output_text: &mut String,
) -> bool {
let out_whitespace = match mode { let out_whitespace = match mode {
CompressionMode::CompressNone | CompressionMode::DiscardNewline => { CompressionMode::CompressNone | CompressionMode::DiscardNewline => {
for ch in text.chars() { for ch in text.chars() {
@ -53,12 +54,13 @@ pub fn transform_text(text: &str,
if is_always_discardable_char(ch) { if is_always_discardable_char(ch) {
// revert whitespace setting, since this char was discarded // revert whitespace setting, since this char was discarded
next_in_whitespace = in_whitespace; next_in_whitespace = in_whitespace;
// TODO: record skipped char // TODO: record skipped char
} else { } else {
// TODO: record kept char // TODO: record kept char
output_text.push(ch); output_text.push(ch);
} }
} else { /* next_in_whitespace; possibly add a space char */ } else {
/* next_in_whitespace; possibly add a space char */
if in_whitespace { if in_whitespace {
// TODO: record skipped char // TODO: record skipped char
} else { } else {
@ -70,17 +72,17 @@ pub fn transform_text(text: &str,
in_whitespace = next_in_whitespace; in_whitespace = next_in_whitespace;
} /* /for str::each_char */ } /* /for str::each_char */
in_whitespace in_whitespace
} },
}; };
return out_whitespace; return out_whitespace;
fn is_in_whitespace(ch: char, mode: CompressionMode) -> bool { fn is_in_whitespace(ch: char, mode: CompressionMode) -> bool {
match (ch, mode) { match (ch, mode) {
(' ', _) => true, (' ', _) => true,
('\t', _) => true, ('\t', _) => true,
('\n', CompressionMode::CompressWhitespaceNewline) => true, ('\n', CompressionMode::CompressWhitespaceNewline) => true,
(_, _) => false (_, _) => false,
} }
} }
@ -89,8 +91,10 @@ pub fn transform_text(text: &str,
return true; return true;
} }
match mode { match mode {
CompressionMode::DiscardNewline | CompressionMode::CompressWhitespaceNewline => ch == '\n', CompressionMode::DiscardNewline | CompressionMode::CompressWhitespaceNewline => {
_ => false ch == '\n'
},
_ => false,
} }
} }
@ -113,7 +117,7 @@ pub fn is_bidi_control(c: char) -> bool {
'\u{202A}'...'\u{202E}' => true, '\u{202A}'...'\u{202E}' => true,
'\u{2066}'...'\u{2069}' => true, '\u{2066}'...'\u{2069}' => true,
'\u{200E}' | '\u{200F}' | '\u{061C}' => true, '\u{200E}' | '\u{200F}' | '\u{061C}' => true,
_ => false _ => false,
} }
} }
@ -143,15 +147,12 @@ pub fn is_cjk(codepoint: char) -> bool {
UnicodeBlock::CJKUnifiedIdeographs | UnicodeBlock::CJKUnifiedIdeographs |
UnicodeBlock::CJKCompatibilityIdeographs | UnicodeBlock::CJKCompatibilityIdeographs |
UnicodeBlock::CJKCompatibilityForms | UnicodeBlock::CJKCompatibilityForms |
UnicodeBlock::HalfwidthandFullwidthForms => { UnicodeBlock::HalfwidthandFullwidthForms => return true,
return true
}
_ => {} _ => {},
} }
} }
// https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Ideographic_Plane // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Ideographic_Plane
unicode_plane(codepoint) == 2 unicode_plane(codepoint) == 2
} }