mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
fonts: Make FontContext
thread-safe and share it per-Layout (#32205)
This allows sharing font templates, fonts, and platform fonts across layout threads. It's the first step toward storing web fonts in the layout versus the shared `FontCacheThread`. Now fonts and font groups have some locking (especially on FreeType), which will probably affect performance. On the other hand, we measured memory usage and this saves roughly 40 megabytes of memory when loading servo.org based on data from the memory profiler. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
parent
8ec5344f70
commit
556bfb7dff
27 changed files with 437 additions and 500 deletions
|
@ -15,6 +15,7 @@ doctest = false
|
|||
|
||||
[dependencies]
|
||||
app_units = { workspace = true }
|
||||
atomic_refcell = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
cssparser = { workspace = true }
|
||||
euclid = { workspace = true }
|
||||
|
|
|
@ -3,11 +3,9 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::borrow::ToOwned;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::time::Instant;
|
||||
use std::{iter, str};
|
||||
|
||||
|
@ -15,6 +13,7 @@ use app_units::Au;
|
|||
use bitflags::bitflags;
|
||||
use euclid::default::{Point2D, Rect, Size2D};
|
||||
use log::debug;
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use servo_atoms::{atom, Atom};
|
||||
use smallvec::SmallVec;
|
||||
|
@ -185,15 +184,21 @@ impl<'a> From<&'a FontStyleStruct> for FontDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct CachedShapeData {
|
||||
glyph_advances: HashMap<GlyphId, FractionalPixel>,
|
||||
glyph_indices: HashMap<char, Option<GlyphId>>,
|
||||
shaped_text: HashMap<ShapeCacheEntry, Arc<GlyphStore>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Font {
|
||||
pub handle: PlatformFont,
|
||||
pub template: FontTemplateRef,
|
||||
pub metrics: FontMetrics,
|
||||
pub descriptor: FontDescriptor,
|
||||
shaper: Option<Shaper>,
|
||||
shape_cache: RefCell<HashMap<ShapeCacheEntry, Arc<GlyphStore>>>,
|
||||
glyph_advance_cache: RefCell<HashMap<u32, FractionalPixel>>,
|
||||
shaper: OnceLock<Shaper>,
|
||||
cached_shape_data: RwLock<CachedShapeData>,
|
||||
pub font_key: FontInstanceKey,
|
||||
|
||||
/// If this is a synthesized small caps font, then this font reference is for
|
||||
|
@ -214,11 +219,10 @@ impl Font {
|
|||
Ok(Font {
|
||||
handle,
|
||||
template,
|
||||
shaper: None,
|
||||
shaper: OnceLock::new(),
|
||||
descriptor,
|
||||
metrics,
|
||||
shape_cache: RefCell::new(HashMap::new()),
|
||||
glyph_advance_cache: RefCell::new(HashMap::new()),
|
||||
cached_shape_data: Default::default(),
|
||||
font_key: FontInstanceKey::default(),
|
||||
synthesized_small_caps,
|
||||
})
|
||||
|
@ -272,52 +276,49 @@ struct ShapeCacheEntry {
|
|||
}
|
||||
|
||||
impl Font {
|
||||
pub fn shape_text(&mut self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
|
||||
pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
|
||||
let this = self as *const Font;
|
||||
let mut shaper = self.shaper.take();
|
||||
|
||||
let lookup_key = ShapeCacheEntry {
|
||||
text: text.to_owned(),
|
||||
options: *options,
|
||||
};
|
||||
let result = self
|
||||
.shape_cache
|
||||
.borrow_mut()
|
||||
.entry(lookup_key)
|
||||
.or_insert_with(|| {
|
||||
let start_time = Instant::now();
|
||||
let mut glyphs = GlyphStore::new(
|
||||
text.len(),
|
||||
options
|
||||
.flags
|
||||
.contains(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG),
|
||||
options.flags.contains(ShapingFlags::RTL_FLAG),
|
||||
);
|
||||
{
|
||||
let cache = self.cached_shape_data.read();
|
||||
if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) {
|
||||
return shaped_text.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if self.can_do_fast_shaping(text, options) {
|
||||
debug!("shape_text: Using ASCII fast path.");
|
||||
self.shape_text_fast(text, options, &mut glyphs);
|
||||
} else {
|
||||
debug!("shape_text: Using Harfbuzz.");
|
||||
if shaper.is_none() {
|
||||
shaper = Some(Shaper::new(this));
|
||||
}
|
||||
shaper
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.shape_text(text, options, &mut glyphs);
|
||||
}
|
||||
let start_time = Instant::now();
|
||||
let mut glyphs = GlyphStore::new(
|
||||
text.len(),
|
||||
options
|
||||
.flags
|
||||
.contains(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG),
|
||||
options.flags.contains(ShapingFlags::RTL_FLAG),
|
||||
);
|
||||
|
||||
let end_time = Instant::now();
|
||||
TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
|
||||
(end_time.duration_since(start_time).as_nanos()) as usize,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
Arc::new(glyphs)
|
||||
})
|
||||
.clone();
|
||||
self.shaper = shaper;
|
||||
result
|
||||
if self.can_do_fast_shaping(text, options) {
|
||||
debug!("shape_text: Using ASCII fast path.");
|
||||
self.shape_text_fast(text, options, &mut glyphs);
|
||||
} else {
|
||||
debug!("shape_text: Using Harfbuzz.");
|
||||
self.shaper
|
||||
.get_or_init(|| Shaper::new(this))
|
||||
.shape_text(text, options, &mut glyphs);
|
||||
}
|
||||
|
||||
let shaped_text = Arc::new(glyphs);
|
||||
let mut cache = self.cached_shape_data.write();
|
||||
cache.shaped_text.insert(lookup_key, shaped_text.clone());
|
||||
|
||||
let end_time = Instant::now();
|
||||
TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
|
||||
(end_time.duration_since(start_time).as_nanos()) as usize,
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
|
||||
shaped_text
|
||||
}
|
||||
|
||||
fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
|
||||
|
@ -377,11 +378,21 @@ impl Font {
|
|||
|
||||
#[inline]
|
||||
pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
|
||||
{
|
||||
let cache = self.cached_shape_data.read();
|
||||
if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
|
||||
return *glyph;
|
||||
}
|
||||
}
|
||||
let codepoint = match self.descriptor.variant {
|
||||
font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
|
||||
font_variant_caps::T::Normal => codepoint,
|
||||
};
|
||||
self.handle.glyph_index(codepoint)
|
||||
let glyph_index = self.handle.glyph_index(codepoint);
|
||||
|
||||
let mut cache = self.cached_shape_data.write();
|
||||
cache.glyph_indices.insert(codepoint, glyph_index);
|
||||
glyph_index
|
||||
}
|
||||
|
||||
pub fn has_glyph_for(&self, codepoint: char) -> bool {
|
||||
|
@ -392,21 +403,27 @@ impl Font {
|
|||
self.handle.glyph_h_kerning(first_glyph, second_glyph)
|
||||
}
|
||||
|
||||
pub fn glyph_h_advance(&self, glyph: GlyphId) -> FractionalPixel {
|
||||
*self
|
||||
.glyph_advance_cache
|
||||
.borrow_mut()
|
||||
.entry(glyph)
|
||||
.or_insert_with(|| {
|
||||
match self.handle.glyph_h_advance(glyph) {
|
||||
Some(adv) => adv,
|
||||
None => LAST_RESORT_GLYPH_ADVANCE as FractionalPixel, // FIXME: Need fallback strategy
|
||||
}
|
||||
})
|
||||
pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
|
||||
{
|
||||
let cache = self.cached_shape_data.read();
|
||||
if let Some(width) = cache.glyph_advances.get(&glyph_id) {
|
||||
return *width;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Need a fallback strategy.
|
||||
let new_width = match self.handle.glyph_h_advance(glyph_id) {
|
||||
Some(adv) => adv,
|
||||
None => LAST_RESORT_GLYPH_ADVANCE as FractionalPixel,
|
||||
};
|
||||
|
||||
let mut cache = self.cached_shape_data.write();
|
||||
cache.glyph_advances.insert(glyph_id, new_width);
|
||||
new_width
|
||||
}
|
||||
}
|
||||
|
||||
pub type FontRef = Rc<RefCell<Font>>;
|
||||
pub type FontRef = Arc<Font>;
|
||||
|
||||
/// A `FontGroup` is a prioritised list of fonts for a given set of font styles. It is used by
|
||||
/// `TextRun` to decide which font to render a character with. If none of the fonts listed in the
|
||||
|
@ -442,14 +459,13 @@ impl FontGroup {
|
|||
/// found, returns None.
|
||||
pub fn find_by_codepoint<S: FontSource>(
|
||||
&mut self,
|
||||
font_context: &mut FontContext<S>,
|
||||
font_context: &FontContext<S>,
|
||||
codepoint: char,
|
||||
) -> Option<FontRef> {
|
||||
let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
|
||||
codepoint.is_ascii_lowercase();
|
||||
let font_or_synthesized_small_caps = |font: FontRef| {
|
||||
if should_look_for_small_caps {
|
||||
let font = font.borrow();
|
||||
if font.synthesized_small_caps.is_some() {
|
||||
return font.synthesized_small_caps.clone();
|
||||
}
|
||||
|
@ -457,7 +473,7 @@ impl FontGroup {
|
|||
Some(font)
|
||||
};
|
||||
|
||||
let glyph_in_font = |font: &FontRef| font.borrow().has_glyph_for(codepoint);
|
||||
let glyph_in_font = |font: &FontRef| font.has_glyph_for(codepoint);
|
||||
let char_in_template =
|
||||
|template: FontTemplateRef| template.char_in_unicode_range(codepoint);
|
||||
|
||||
|
@ -466,7 +482,7 @@ impl FontGroup {
|
|||
}
|
||||
|
||||
if let Some(ref last_matching_fallback) = self.last_matching_fallback {
|
||||
if char_in_template(last_matching_fallback.borrow().template.clone()) &&
|
||||
if char_in_template(last_matching_fallback.template.clone()) &&
|
||||
glyph_in_font(last_matching_fallback)
|
||||
{
|
||||
return font_or_synthesized_small_caps(last_matching_fallback.clone());
|
||||
|
@ -487,7 +503,7 @@ impl FontGroup {
|
|||
}
|
||||
|
||||
/// Find the first available font in the group, or the first available fallback font.
|
||||
pub fn first<S: FontSource>(&mut self, font_context: &mut FontContext<S>) -> Option<FontRef> {
|
||||
pub fn first<S: FontSource>(&mut self, font_context: &FontContext<S>) -> Option<FontRef> {
|
||||
// From https://drafts.csswg.org/css-fonts/#first-available-font:
|
||||
// > The first available font, used for example in the definition of font-relative lengths
|
||||
// > such as ex or in the definition of the line-height property, is defined to be the first
|
||||
|
@ -506,7 +522,7 @@ impl FontGroup {
|
|||
/// a suitable font.
|
||||
fn find<S, TemplatePredicate, FontPredicate>(
|
||||
&mut self,
|
||||
font_context: &mut FontContext<S>,
|
||||
font_context: &FontContext<S>,
|
||||
template_predicate: TemplatePredicate,
|
||||
font_predicate: FontPredicate,
|
||||
) -> Option<FontRef>
|
||||
|
@ -535,7 +551,7 @@ impl FontGroup {
|
|||
/// used to refine the list of family names which will be tried.
|
||||
fn find_fallback<S, TemplatePredicate, FontPredicate>(
|
||||
&mut self,
|
||||
font_context: &mut FontContext<S>,
|
||||
font_context: &FontContext<S>,
|
||||
codepoint: Option<char>,
|
||||
template_predicate: TemplatePredicate,
|
||||
font_predicate: FontPredicate,
|
||||
|
@ -602,7 +618,7 @@ impl FontGroupFamily {
|
|||
fn find<S, TemplatePredicate, FontPredicate>(
|
||||
&mut self,
|
||||
font_descriptor: &FontDescriptor,
|
||||
font_context: &mut FontContext<S>,
|
||||
font_context: &FontContext<S>,
|
||||
template_predicate: &TemplatePredicate,
|
||||
font_predicate: &FontPredicate,
|
||||
) -> Option<FontRef>
|
||||
|
@ -633,7 +649,7 @@ impl FontGroupFamily {
|
|||
fn members<'a, S: FontSource>(
|
||||
&'a mut self,
|
||||
font_descriptor: &FontDescriptor,
|
||||
font_context: &mut FontContext<S>,
|
||||
font_context: &FontContext<S>,
|
||||
) -> impl Iterator<Item = &mut FontGroupFamilyMember> + 'a {
|
||||
let family_descriptor = &self.family_descriptor;
|
||||
let members = self.members.get_or_insert_with(|| {
|
||||
|
|
|
@ -3,14 +3,13 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::borrow::ToOwned;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, RangeInclusive};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{f32, fmt, mem, thread};
|
||||
|
||||
use app_units::Au;
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
use gfx_traits::WebrenderApi;
|
||||
use ipc_channel::ipc::{self, IpcBytesSender, IpcReceiver, IpcSender};
|
||||
use log::{debug, trace};
|
||||
|
@ -125,7 +124,8 @@ impl FontTemplates {
|
|||
return;
|
||||
}
|
||||
}
|
||||
self.templates.push(Rc::new(RefCell::new(new_template)));
|
||||
self.templates
|
||||
.push(Arc::new(AtomicRefCell::new(new_template)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -730,7 +730,7 @@ impl FontSource for FontCacheThread {
|
|||
.into_iter()
|
||||
.map(|serialized_font_template| {
|
||||
let font_data = serialized_font_template.bytes_receiver.recv().ok();
|
||||
Rc::new(RefCell::new(FontTemplate {
|
||||
Arc::new(AtomicRefCell::new(FontTemplate {
|
||||
identifier: serialized_font_template.identifier,
|
||||
descriptor: serialized_font_template.descriptor.clone(),
|
||||
data: font_data.map(Arc::new),
|
||||
|
|
|
@ -2,17 +2,16 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::default::Default;
|
||||
use std::hash::{BuildHasherDefault, Hash, Hasher};
|
||||
use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use app_units::Au;
|
||||
use fnv::FnvHasher;
|
||||
use log::debug;
|
||||
use servo_arc::Arc;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use servo_arc::Arc as ServoArc;
|
||||
use style::computed_values::font_variant_caps::T as FontVariantCaps;
|
||||
use style::properties::style_structs::Font as FontStyleStruct;
|
||||
use webrender_api::{FontInstanceFlags, FontInstanceKey};
|
||||
|
@ -25,10 +24,6 @@ use crate::platform::core_text_font_cache::CoreTextFontCache;
|
|||
|
||||
static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h)
|
||||
|
||||
/// An epoch for the font context cache. The cache is flushed if the current epoch does not match
|
||||
/// this one.
|
||||
static FONT_CACHE_EPOCH: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub trait FontSource {
|
||||
fn get_font_instance(
|
||||
&mut self,
|
||||
|
@ -49,48 +44,48 @@ pub trait FontSource {
|
|||
/// required.
|
||||
#[derive(Debug)]
|
||||
pub struct FontContext<S: FontSource> {
|
||||
font_source: S,
|
||||
font_source: Mutex<S>,
|
||||
|
||||
// TODO: The font context holds a strong ref to the cached fonts
|
||||
// so they will never be released. Find out a good time to drop them.
|
||||
// See bug https://github.com/servo/servo/issues/3300
|
||||
font_cache: HashMap<FontCacheKey, Option<FontRef>>,
|
||||
font_template_cache: HashMap<FontTemplateCacheKey, Vec<FontTemplateRef>>,
|
||||
|
||||
font_cache: RwLock<HashMap<FontCacheKey, Option<FontRef>>>,
|
||||
font_template_cache: RwLock<HashMap<FontTemplateCacheKey, Vec<FontTemplateRef>>>,
|
||||
font_group_cache:
|
||||
HashMap<FontGroupCacheKey, Rc<RefCell<FontGroup>>, BuildHasherDefault<FnvHasher>>,
|
||||
|
||||
epoch: usize,
|
||||
RwLock<HashMap<FontGroupCacheKey, Arc<RwLock<FontGroup>>, BuildHasherDefault<FnvHasher>>>,
|
||||
}
|
||||
|
||||
impl<S: FontSource> FontContext<S> {
|
||||
pub fn new(font_source: S) -> FontContext<S> {
|
||||
#[allow(clippy::default_constructed_unit_structs)]
|
||||
FontContext {
|
||||
font_source,
|
||||
font_cache: HashMap::new(),
|
||||
font_template_cache: HashMap::new(),
|
||||
font_group_cache: HashMap::with_hasher(Default::default()),
|
||||
epoch: 0,
|
||||
font_source: Mutex::new(font_source),
|
||||
font_cache: RwLock::default(),
|
||||
font_template_cache: RwLock::default(),
|
||||
font_group_cache: RwLock::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn expire_font_caches_if_necessary(&mut self) {
|
||||
let current_epoch = FONT_CACHE_EPOCH.load(Ordering::SeqCst);
|
||||
if current_epoch == self.epoch {
|
||||
return;
|
||||
}
|
||||
/// Invalidate all caches that this [`FontContext`] holds and any in-process platform-specific
|
||||
/// caches.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This should never be called when more than one thread is using the [`FontContext`] or it
|
||||
/// may leave the context in an inconsistent state.
|
||||
pub fn invalidate_caches(&self) {
|
||||
#[cfg(target_os = "macos")]
|
||||
CoreTextFontCache::clear_core_text_font_cache();
|
||||
|
||||
self.font_cache.clear();
|
||||
self.font_template_cache.clear();
|
||||
self.font_group_cache.clear();
|
||||
self.epoch = current_epoch
|
||||
self.font_cache.write().clear();
|
||||
self.font_template_cache.write().clear();
|
||||
self.font_group_cache.write().clear();
|
||||
}
|
||||
|
||||
/// Returns a `FontGroup` representing fonts which can be used for layout, given the `style`.
|
||||
/// Font groups are cached, so subsequent calls with the same `style` will return a reference
|
||||
/// to an existing `FontGroup`.
|
||||
pub fn font_group(&mut self, style: Arc<FontStyleStruct>) -> Rc<RefCell<FontGroup>> {
|
||||
pub fn font_group(&self, style: ServoArc<FontStyleStruct>) -> Arc<RwLock<FontGroup>> {
|
||||
let font_size = style.font_size.computed_size().into();
|
||||
self.font_group_with_size(style, font_size)
|
||||
}
|
||||
|
@ -98,27 +93,27 @@ impl<S: FontSource> FontContext<S> {
|
|||
/// Like [`Self::font_group`], but overriding the size found in the [`FontStyleStruct`] with the given size
|
||||
/// in pixels.
|
||||
pub fn font_group_with_size(
|
||||
&mut self,
|
||||
style: Arc<FontStyleStruct>,
|
||||
&self,
|
||||
style: ServoArc<FontStyleStruct>,
|
||||
size: Au,
|
||||
) -> Rc<RefCell<FontGroup>> {
|
||||
self.expire_font_caches_if_necessary();
|
||||
|
||||
) -> Arc<RwLock<FontGroup>> {
|
||||
let cache_key = FontGroupCacheKey { size, style };
|
||||
|
||||
if let Some(font_group) = self.font_group_cache.get(&cache_key) {
|
||||
if let Some(font_group) = self.font_group_cache.read().get(&cache_key) {
|
||||
return font_group.clone();
|
||||
}
|
||||
|
||||
let font_group = Rc::new(RefCell::new(FontGroup::new(&cache_key.style)));
|
||||
self.font_group_cache.insert(cache_key, font_group.clone());
|
||||
let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style)));
|
||||
self.font_group_cache
|
||||
.write()
|
||||
.insert(cache_key, font_group.clone());
|
||||
font_group
|
||||
}
|
||||
|
||||
/// Returns a font matching the parameters. Fonts are cached, so repeated calls will return a
|
||||
/// reference to the same underlying `Font`.
|
||||
pub fn font(
|
||||
&mut self,
|
||||
&self,
|
||||
font_template: FontTemplateRef,
|
||||
font_descriptor: &FontDescriptor,
|
||||
) -> Option<FontRef> {
|
||||
|
@ -130,7 +125,7 @@ impl<S: FontSource> FontContext<S> {
|
|||
}
|
||||
|
||||
fn get_font_maybe_synthesizing_small_caps(
|
||||
&mut self,
|
||||
&self,
|
||||
font_template: FontTemplateRef,
|
||||
font_descriptor: &FontDescriptor,
|
||||
synthesize_small_caps: bool,
|
||||
|
@ -157,27 +152,28 @@ impl<S: FontSource> FontContext<S> {
|
|||
font_descriptor: font_descriptor.clone(),
|
||||
};
|
||||
|
||||
self.font_cache.get(&cache_key).cloned().unwrap_or_else(|| {
|
||||
debug!(
|
||||
"FontContext::font cache miss for font_template={:?} font_descriptor={:?}",
|
||||
font_template, font_descriptor
|
||||
);
|
||||
if let Some(font) = self.font_cache.read().get(&cache_key).cloned() {
|
||||
return font;
|
||||
}
|
||||
|
||||
let font = self
|
||||
.create_font(
|
||||
font_template,
|
||||
font_descriptor.to_owned(),
|
||||
synthesized_small_caps_font,
|
||||
)
|
||||
.ok();
|
||||
self.font_cache.insert(cache_key, font.clone());
|
||||
debug!(
|
||||
"FontContext::font cache miss for font_template={:?} font_descriptor={:?}",
|
||||
font_template, font_descriptor
|
||||
);
|
||||
|
||||
font
|
||||
})
|
||||
let font = self
|
||||
.create_font(
|
||||
font_template,
|
||||
font_descriptor.to_owned(),
|
||||
synthesized_small_caps_font,
|
||||
)
|
||||
.ok();
|
||||
self.font_cache.write().insert(cache_key, font.clone());
|
||||
font
|
||||
}
|
||||
|
||||
pub fn matching_templates(
|
||||
&mut self,
|
||||
&self,
|
||||
descriptor_to_match: &FontDescriptor,
|
||||
family_descriptor: &FontFamilyDescriptor,
|
||||
) -> Vec<FontTemplateRef> {
|
||||
|
@ -186,27 +182,31 @@ impl<S: FontSource> FontContext<S> {
|
|||
family_descriptor: family_descriptor.clone(),
|
||||
};
|
||||
|
||||
self.font_template_cache.get(&cache_key).cloned().unwrap_or_else(|| {
|
||||
debug!(
|
||||
"FontContext::font_template cache miss for template_descriptor={:?} family_descriptor={:?}",
|
||||
descriptor_to_match,
|
||||
family_descriptor
|
||||
);
|
||||
if let Some(templates) = self.font_template_cache.read().get(&cache_key).cloned() {
|
||||
return templates;
|
||||
}
|
||||
|
||||
let template_info = self.font_source.find_matching_font_templates(
|
||||
descriptor_to_match,
|
||||
family_descriptor.clone(),
|
||||
);
|
||||
debug!(
|
||||
"FontContext::font_template cache miss for template_descriptor={:?} family_descriptor={:?}",
|
||||
descriptor_to_match,
|
||||
family_descriptor
|
||||
);
|
||||
|
||||
self.font_template_cache.insert(cache_key, template_info.clone());
|
||||
template_info
|
||||
})
|
||||
let templates = self
|
||||
.font_source
|
||||
.lock()
|
||||
.find_matching_font_templates(descriptor_to_match, family_descriptor.clone());
|
||||
|
||||
self.font_template_cache
|
||||
.write()
|
||||
.insert(cache_key, templates.clone());
|
||||
templates
|
||||
}
|
||||
|
||||
/// Create a `Font` for use in layout calculations, from a `FontTemplateData` returned by the
|
||||
/// cache thread and a `FontDescriptor` which contains the styling parameters.
|
||||
fn create_font(
|
||||
&mut self,
|
||||
&self,
|
||||
font_template: FontTemplateRef,
|
||||
font_descriptor: FontDescriptor,
|
||||
synthesized_small_caps: Option<FontRef>,
|
||||
|
@ -216,13 +216,13 @@ impl<S: FontSource> FontContext<S> {
|
|||
font_descriptor.clone(),
|
||||
synthesized_small_caps,
|
||||
)?;
|
||||
font.font_key = self.font_source.get_font_instance(
|
||||
font.font_key = self.font_source.lock().get_font_instance(
|
||||
font_template.identifier(),
|
||||
font_descriptor.pt_size,
|
||||
font.webrender_font_instance_flags(),
|
||||
);
|
||||
|
||||
Ok(Rc::new(RefCell::new(font)))
|
||||
Ok(Arc::new(font))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,7 +240,7 @@ struct FontTemplateCacheKey {
|
|||
|
||||
#[derive(Debug)]
|
||||
struct FontGroupCacheKey {
|
||||
style: Arc<FontStyleStruct>,
|
||||
style: ServoArc<FontStyleStruct>,
|
||||
size: Au,
|
||||
}
|
||||
|
||||
|
@ -260,11 +260,3 @@ impl Hash for FontGroupCacheKey {
|
|||
self.style.hash.hash(hasher)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn invalidate_font_caches() {
|
||||
FONT_CACHE_EPOCH.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
CoreTextFontCache::clear_core_text_font_cache();
|
||||
}
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::{Debug, Error, Formatter};
|
||||
use std::ops::RangeInclusive;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use atomic_refcell::AtomicRefCell;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use servo_url::ServoUrl;
|
||||
use style::computed_values::font_stretch::T as FontStretch;
|
||||
|
@ -22,7 +21,7 @@ use crate::platform::font::PlatformFont;
|
|||
use crate::platform::font_list::LocalFontIdentifier;
|
||||
|
||||
/// A reference to a [`FontTemplate`] with shared ownership and mutability.
|
||||
pub type FontTemplateRef = Rc<RefCell<FontTemplate>>;
|
||||
pub type FontTemplateRef = Arc<AtomicRefCell<FontTemplate>>;
|
||||
|
||||
/// Describes how to select a font from a given family. This is very basic at the moment and needs
|
||||
/// to be expanded or refactored when we support more of the font styling parameters.
|
||||
|
|
|
@ -9,14 +9,15 @@ use std::{mem, ptr};
|
|||
|
||||
use app_units::Au;
|
||||
use freetype::freetype::{
|
||||
FT_Done_Face, FT_F26Dot6, FT_Face, FT_FaceRec, FT_Get_Char_Index, FT_Get_Kerning,
|
||||
FT_Get_Sfnt_Table, FT_GlyphSlot, FT_Int32, FT_Kerning_Mode, FT_Load_Glyph, FT_Load_Sfnt_Table,
|
||||
FT_Long, FT_New_Memory_Face, FT_Set_Char_Size, FT_Sfnt_Tag, FT_SizeRec, FT_Size_Metrics,
|
||||
FT_UInt, FT_ULong, FT_Vector, FT_STYLE_FLAG_ITALIC,
|
||||
FT_Done_Face, FT_F26Dot6, FT_Face, FT_Get_Char_Index, FT_Get_Kerning, FT_Get_Sfnt_Table,
|
||||
FT_GlyphSlot, FT_Int32, FT_Kerning_Mode, FT_Load_Glyph, FT_Load_Sfnt_Table, FT_Long,
|
||||
FT_New_Memory_Face, FT_Set_Char_Size, FT_Sfnt_Tag, FT_SizeRec, FT_Size_Metrics, FT_UInt,
|
||||
FT_ULong, FT_Vector, FT_STYLE_FLAG_ITALIC,
|
||||
};
|
||||
use freetype::succeeded;
|
||||
use freetype::tt_os2::TT_OS2;
|
||||
use log::debug;
|
||||
use parking_lot::ReentrantMutex;
|
||||
use style::computed_values::font_stretch::T as FontStretch;
|
||||
use style::computed_values::font_weight::T as FontWeight;
|
||||
use style::values::computed::font::FontStyle;
|
||||
|
@ -75,19 +76,26 @@ pub struct PlatformFont {
|
|||
/// The font data itself, which must stay valid for the lifetime of the
|
||||
/// platform [`FT_Face`].
|
||||
font_data: Arc<Vec<u8>>,
|
||||
face: FT_Face,
|
||||
face: ReentrantMutex<FT_Face>,
|
||||
can_do_fast_shaping: bool,
|
||||
}
|
||||
|
||||
// FT_Face can be used in multiple threads, but from only one thread at a time.
|
||||
// It's protected with a ReentrantMutex for PlatformFont.
|
||||
// See https://freetype.org/freetype2/docs/reference/ft2-face_creation.html#ft_face.
|
||||
unsafe impl Sync for PlatformFont {}
|
||||
unsafe impl Send for PlatformFont {}
|
||||
|
||||
impl Drop for PlatformFont {
|
||||
fn drop(&mut self) {
|
||||
assert!(!self.face.is_null());
|
||||
let face = self.face.lock();
|
||||
assert!(!face.is_null());
|
||||
unsafe {
|
||||
// The FreeType documentation says that both `FT_New_Face` and `FT_Done_Face`
|
||||
// should be protected by a mutex.
|
||||
// See https://freetype.org/freetype2/docs/reference/ft2-library_setup.html.
|
||||
let _guard = FreeTypeLibraryHandle::get().lock();
|
||||
if !succeeded(FT_Done_Face(self.face)) {
|
||||
if !succeeded(FT_Done_Face(*face)) {
|
||||
panic!("FT_Done_Face failed");
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +142,7 @@ impl PlatformFontMethods for PlatformFont {
|
|||
) -> Result<PlatformFont, &'static str> {
|
||||
let face = create_face(data.clone(), face_index, pt_size)?;
|
||||
let mut handle = PlatformFont {
|
||||
face,
|
||||
face: ReentrantMutex::new(face),
|
||||
font_data: data,
|
||||
can_do_fast_shaping: false,
|
||||
};
|
||||
|
@ -147,7 +155,8 @@ impl PlatformFontMethods for PlatformFont {
|
|||
}
|
||||
|
||||
fn descriptor(&self) -> FontTemplateDescriptor {
|
||||
let style = if unsafe { (*self.face).style_flags & FT_STYLE_FLAG_ITALIC as c_long != 0 } {
|
||||
let face = self.face.lock();
|
||||
let style = if unsafe { (**face).style_flags & FT_STYLE_FLAG_ITALIC as c_long != 0 } {
|
||||
FontStyle::ITALIC
|
||||
} else {
|
||||
FontStyle::NORMAL
|
||||
|
@ -178,9 +187,11 @@ impl PlatformFontMethods for PlatformFont {
|
|||
}
|
||||
|
||||
fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
|
||||
assert!(!self.face.is_null());
|
||||
let face = self.face.lock();
|
||||
assert!(!face.is_null());
|
||||
|
||||
unsafe {
|
||||
let idx = FT_Get_Char_Index(self.face, codepoint as FT_ULong);
|
||||
let idx = FT_Get_Char_Index(*face, codepoint as FT_ULong);
|
||||
if idx != 0 as FT_UInt {
|
||||
Some(idx as GlyphId)
|
||||
} else {
|
||||
|
@ -194,11 +205,13 @@ impl PlatformFontMethods for PlatformFont {
|
|||
}
|
||||
|
||||
fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
|
||||
assert!(!self.face.is_null());
|
||||
let face = self.face.lock();
|
||||
assert!(!face.is_null());
|
||||
|
||||
let mut delta = FT_Vector { x: 0, y: 0 };
|
||||
unsafe {
|
||||
FT_Get_Kerning(
|
||||
self.face,
|
||||
*face,
|
||||
first_glyph,
|
||||
second_glyph,
|
||||
FT_Kerning_Mode::FT_KERNING_DEFAULT as FT_UInt,
|
||||
|
@ -213,11 +226,13 @@ impl PlatformFontMethods for PlatformFont {
|
|||
}
|
||||
|
||||
fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
|
||||
assert!(!self.face.is_null());
|
||||
let face = self.face.lock();
|
||||
assert!(!face.is_null());
|
||||
|
||||
unsafe {
|
||||
let res = FT_Load_Glyph(self.face, glyph as FT_UInt, GLYPH_LOAD_FLAGS);
|
||||
let res = FT_Load_Glyph(*face, glyph as FT_UInt, GLYPH_LOAD_FLAGS);
|
||||
if succeeded(res) {
|
||||
let void_glyph = (*self.face).glyph;
|
||||
let void_glyph = (**face).glyph;
|
||||
let slot: FT_GlyphSlot = void_glyph;
|
||||
assert!(!slot.is_null());
|
||||
let advance = (*slot).metrics.horiAdvance;
|
||||
|
@ -232,8 +247,8 @@ impl PlatformFontMethods for PlatformFont {
|
|||
}
|
||||
|
||||
fn metrics(&self) -> FontMetrics {
|
||||
/* TODO(Issue #76): complete me */
|
||||
let face = self.face_rec_mut();
|
||||
let face = self.face.lock();
|
||||
let face = unsafe { **face };
|
||||
|
||||
let underline_size = self.font_units_to_au(face.underline_thickness as f64);
|
||||
let underline_offset = self.font_units_to_au(face.underline_position as f64);
|
||||
|
@ -294,24 +309,19 @@ impl PlatformFontMethods for PlatformFont {
|
|||
}
|
||||
|
||||
fn table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
|
||||
let face = self.face.lock();
|
||||
let tag = tag as FT_ULong;
|
||||
|
||||
unsafe {
|
||||
// Get the length
|
||||
let mut len = 0;
|
||||
if !succeeded(FT_Load_Sfnt_Table(
|
||||
self.face,
|
||||
tag,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
&mut len,
|
||||
)) {
|
||||
if !succeeded(FT_Load_Sfnt_Table(*face, tag, 0, ptr::null_mut(), &mut len)) {
|
||||
return None;
|
||||
}
|
||||
// Get the bytes
|
||||
let mut buf = vec![0u8; len as usize];
|
||||
if !succeeded(FT_Load_Sfnt_Table(
|
||||
self.face,
|
||||
*face,
|
||||
tag,
|
||||
0,
|
||||
buf.as_mut_ptr(),
|
||||
|
@ -343,9 +353,10 @@ impl<'a> PlatformFont {
|
|||
}
|
||||
|
||||
fn has_table(&self, tag: FontTableTag) -> bool {
|
||||
let face = self.face.lock();
|
||||
unsafe {
|
||||
succeeded(FT_Load_Sfnt_Table(
|
||||
self.face,
|
||||
*face,
|
||||
tag as FT_ULong,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
|
@ -354,13 +365,9 @@ impl<'a> PlatformFont {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref)] // Intended for this function
|
||||
fn face_rec_mut(&'a self) -> &'a mut FT_FaceRec {
|
||||
unsafe { &mut (*self.face) }
|
||||
}
|
||||
|
||||
fn font_units_to_au(&self, value: f64) -> Au {
|
||||
let face = self.face_rec_mut();
|
||||
let face = self.face.lock();
|
||||
let face = unsafe { **face };
|
||||
|
||||
// face.size is a *c_void in the bindings, presumably to avoid
|
||||
// recursive structural types
|
||||
|
@ -377,9 +384,10 @@ impl<'a> PlatformFont {
|
|||
}
|
||||
|
||||
fn os2_table(&self) -> Option<OS2Table> {
|
||||
let face = self.face.lock();
|
||||
|
||||
unsafe {
|
||||
let os2 =
|
||||
FT_Get_Sfnt_Table(self.face_rec_mut(), FT_Sfnt_Tag::FT_SFNT_OS2) as *mut TT_OS2;
|
||||
let os2 = FT_Get_Sfnt_Table(*face, FT_Sfnt_Tag::FT_SFNT_OS2) as *mut TT_OS2;
|
||||
let valid = !os2.is_null() && (*os2).version != 0xffff;
|
||||
|
||||
if !valid {
|
||||
|
|
|
@ -63,6 +63,17 @@ pub struct PlatformFont {
|
|||
can_do_fast_shaping: bool,
|
||||
}
|
||||
|
||||
// From https://developer.apple.com/documentation/coretext:
|
||||
// > All individual functions in Core Text are thread-safe. Font objects (CTFont,
|
||||
// > CTFontDescriptor, and associated objects) can be used simultaneously by multiple
|
||||
// > operations, work queues, or threads. However, the layout objects (CTTypesetter,
|
||||
// > CTFramesetter, CTRun, CTLine, CTFrame, and associated objects) should be used in a
|
||||
// > single operation, work queue, or thread.
|
||||
//
|
||||
// The other element is a read-only CachedKernTable which is stored in a CFData.
|
||||
unsafe impl Sync for PlatformFont {}
|
||||
unsafe impl Send for PlatformFont {}
|
||||
|
||||
impl PlatformFont {
|
||||
/// Cache all the data needed for basic horizontal kerning. This is used only as a fallback or
|
||||
/// fast path (when the GPOS table is missing or unnecessary) so it needn't handle every case.
|
||||
|
|
|
@ -82,6 +82,13 @@ pub struct PlatformFont {
|
|||
scaled_du_to_px: f32,
|
||||
}
|
||||
|
||||
// Based on information from the Skia codebase, it seems that DirectWrite APIs from
|
||||
// Windows 10 and beyond are thread safe. If problems arise from this, we can protect the
|
||||
// platform font with a Mutex.
|
||||
// See https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/src/ports/SkScalerContext_win_dw.cpp;l=56;bpv=0;bpt=1.
|
||||
unsafe impl Sync for PlatformFont {}
|
||||
unsafe impl Send for PlatformFont {}
|
||||
|
||||
struct Nondebug<T>(T);
|
||||
|
||||
impl<T> fmt::Debug for Nondebug<T> {
|
||||
|
|
|
@ -150,22 +150,26 @@ fn font_family(names: Vec<&str>) -> FontFamily {
|
|||
#[test]
|
||||
fn test_font_group_is_cached_by_style() {
|
||||
let source = TestFontSource::new();
|
||||
let mut context = FontContext::new(source);
|
||||
let context = FontContext::new(source);
|
||||
|
||||
let style1 = style();
|
||||
|
||||
let mut style2 = style();
|
||||
style2.set_font_style(FontStyle::ITALIC);
|
||||
|
||||
assert_eq!(
|
||||
context.font_group(Arc::new(style1.clone())).as_ptr(),
|
||||
context.font_group(Arc::new(style1.clone())).as_ptr(),
|
||||
assert!(
|
||||
std::ptr::eq(
|
||||
&*context.font_group(Arc::new(style1.clone())).read(),
|
||||
&*context.font_group(Arc::new(style1.clone())).read()
|
||||
),
|
||||
"the same font group should be returned for two styles with the same hash"
|
||||
);
|
||||
|
||||
assert_ne!(
|
||||
context.font_group(Arc::new(style1.clone())).as_ptr(),
|
||||
context.font_group(Arc::new(style2.clone())).as_ptr(),
|
||||
assert!(
|
||||
!std::ptr::eq(
|
||||
&*context.font_group(Arc::new(style1.clone())).read(),
|
||||
&*context.font_group(Arc::new(style2.clone())).read()
|
||||
),
|
||||
"different font groups should be returned for two styles with different hashes"
|
||||
)
|
||||
}
|
||||
|
@ -181,12 +185,9 @@ fn test_font_group_find_by_codepoint() {
|
|||
|
||||
let group = context.font_group(Arc::new(style));
|
||||
|
||||
let font = group
|
||||
.borrow_mut()
|
||||
.find_by_codepoint(&mut context, 'a')
|
||||
.unwrap();
|
||||
let font = group.write().find_by_codepoint(&mut context, 'a').unwrap();
|
||||
assert_eq!(
|
||||
font.borrow().identifier(),
|
||||
font.identifier(),
|
||||
TestFontSource::identifier_for_font_name("csstest-ascii")
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -195,12 +196,9 @@ fn test_font_group_find_by_codepoint() {
|
|||
"only the first font in the list should have been loaded"
|
||||
);
|
||||
|
||||
let font = group
|
||||
.borrow_mut()
|
||||
.find_by_codepoint(&mut context, 'a')
|
||||
.unwrap();
|
||||
let font = group.write().find_by_codepoint(&mut context, 'a').unwrap();
|
||||
assert_eq!(
|
||||
font.borrow().identifier(),
|
||||
font.identifier(),
|
||||
TestFontSource::identifier_for_font_name("csstest-ascii")
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -209,12 +207,9 @@ fn test_font_group_find_by_codepoint() {
|
|||
"we shouldn't load the same font a second time"
|
||||
);
|
||||
|
||||
let font = group
|
||||
.borrow_mut()
|
||||
.find_by_codepoint(&mut context, 'á')
|
||||
.unwrap();
|
||||
let font = group.write().find_by_codepoint(&mut context, 'á').unwrap();
|
||||
assert_eq!(
|
||||
font.borrow().identifier(),
|
||||
font.identifier(),
|
||||
TestFontSource::identifier_for_font_name("csstest-basic-regular")
|
||||
);
|
||||
assert_eq!(count.get(), 2, "both fonts should now have been loaded");
|
||||
|
@ -230,22 +225,16 @@ fn test_font_fallback() {
|
|||
|
||||
let group = context.font_group(Arc::new(style));
|
||||
|
||||
let font = group
|
||||
.borrow_mut()
|
||||
.find_by_codepoint(&mut context, 'a')
|
||||
.unwrap();
|
||||
let font = group.write().find_by_codepoint(&mut context, 'a').unwrap();
|
||||
assert_eq!(
|
||||
font.borrow().identifier(),
|
||||
font.identifier(),
|
||||
TestFontSource::identifier_for_font_name("csstest-ascii"),
|
||||
"a family in the group should be used if there is a matching glyph"
|
||||
);
|
||||
|
||||
let font = group
|
||||
.borrow_mut()
|
||||
.find_by_codepoint(&mut context, 'á')
|
||||
.unwrap();
|
||||
let font = group.write().find_by_codepoint(&mut context, 'á').unwrap();
|
||||
assert_eq!(
|
||||
font.borrow().identifier(),
|
||||
font.identifier(),
|
||||
TestFontSource::identifier_for_font_name("csstest-basic-regular"),
|
||||
"a fallback font should be used if there is no matching glyph in the group"
|
||||
);
|
||||
|
@ -255,7 +244,7 @@ fn test_font_fallback() {
|
|||
fn test_font_template_is_cached() {
|
||||
let source = TestFontSource::new();
|
||||
let count = source.find_font_count.clone();
|
||||
let mut context = FontContext::new(source);
|
||||
let context = FontContext::new(source);
|
||||
|
||||
let mut font_descriptor = FontDescriptor {
|
||||
weight: FontWeight::normal(),
|
||||
|
@ -280,8 +269,7 @@ fn test_font_template_is_cached() {
|
|||
.unwrap();
|
||||
|
||||
assert_ne!(
|
||||
font1.borrow().descriptor.pt_size,
|
||||
font2.borrow().descriptor.pt_size,
|
||||
font1.descriptor.pt_size, font2.descriptor.pt_size,
|
||||
"the same font should not have been returned"
|
||||
);
|
||||
|
||||
|
|
|
@ -130,6 +130,12 @@ pub struct Shaper {
|
|||
font: *const Font,
|
||||
}
|
||||
|
||||
// The HarfBuzz API is thread safe as well as our `Font`, so we can make the data
|
||||
// structures here as thread-safe as well. This doesn't seem to be documented,
|
||||
// but was expressed as one of the original goals of the HarfBuzz API.
|
||||
unsafe impl Sync for Shaper {}
|
||||
unsafe impl Send for Shaper {}
|
||||
|
||||
impl Drop for Shaper {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
|
|
|
@ -16,7 +16,7 @@ use unicode_bidi as bidi;
|
|||
use webrender_api::FontInstanceKey;
|
||||
use xi_unicode::LineBreakLeafIter;
|
||||
|
||||
use crate::font::{Font, FontMetrics, RunMetrics, ShapingFlags, ShapingOptions};
|
||||
use crate::font::{FontMetrics, FontRef, RunMetrics, ShapingFlags, ShapingOptions};
|
||||
use crate::text::glyph::{ByteIndex, GlyphStore};
|
||||
|
||||
thread_local! {
|
||||
|
@ -180,13 +180,14 @@ impl<'a> Iterator for CharacterSliceIterator<'a> {
|
|||
impl<'a> TextRun {
|
||||
/// Constructs a new text run. Also returns if there is a line break at the beginning
|
||||
pub fn new(
|
||||
font: &mut Font,
|
||||
font: FontRef,
|
||||
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.clone(), &text, options, breaker);
|
||||
(
|
||||
TextRun {
|
||||
text: Arc::new(text),
|
||||
|
@ -202,7 +203,7 @@ impl<'a> TextRun {
|
|||
}
|
||||
|
||||
pub fn break_and_shape(
|
||||
font: &mut Font,
|
||||
font: FontRef,
|
||||
text: &str,
|
||||
options: &ShapingOptions,
|
||||
breaker: &mut Option<LineBreakLeafIter>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue