mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Format gfx text #21373
This commit is contained in:
parent
41a6c0cc39
commit
aa4a8eb88d
6 changed files with 420 additions and 266 deletions
|
@ -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()];
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue