mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00: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
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1952,6 +1952,7 @@ name = "gfx"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"app_units",
|
||||
"atomic_refcell",
|
||||
"bitflags 2.5.0",
|
||||
"byteorder",
|
||||
"core-foundation",
|
||||
|
|
|
@ -115,6 +115,7 @@ to_shmem = { git = "https://github.com/servo/stylo", branch = "2024-04-16" }
|
|||
tokio = "1"
|
||||
tokio-rustls = "0.24"
|
||||
tungstenite = "0.20"
|
||||
uluru = "3.0"
|
||||
unicode-bidi = "0.3.15"
|
||||
unicode-script = "0.5"
|
||||
unicode-segmentation = "1.1.0"
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
* 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::mem;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::Arc;
|
||||
|
||||
use canvas_traits::canvas::*;
|
||||
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
|
||||
|
@ -359,21 +358,6 @@ pub enum Filter {
|
|||
Nearest,
|
||||
}
|
||||
|
||||
pub(crate) type CanvasFontContext = FontContext<FontCacheThread>;
|
||||
|
||||
thread_local!(static FONT_CONTEXT: RefCell<Option<CanvasFontContext>> = RefCell::new(None));
|
||||
|
||||
pub(crate) fn with_thread_local_font_context<F, R>(canvas_data: &CanvasData, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut CanvasFontContext) -> R,
|
||||
{
|
||||
FONT_CONTEXT.with(|font_context| {
|
||||
f(font_context.borrow_mut().get_or_insert_with(|| {
|
||||
FontContext::new(canvas_data.font_cache_thread.lock().unwrap().clone())
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
pub struct CanvasData<'a> {
|
||||
backend: Box<dyn Backend>,
|
||||
drawtarget: Box<dyn GenericDrawTarget>,
|
||||
|
@ -386,7 +370,7 @@ pub struct CanvasData<'a> {
|
|||
old_image_key: Option<ImageKey>,
|
||||
/// An old webrender image key that can be deleted when the current epoch ends.
|
||||
very_old_image_key: Option<ImageKey>,
|
||||
font_cache_thread: Mutex<FontCacheThread>,
|
||||
font_context: Arc<FontContext<FontCacheThread>>,
|
||||
}
|
||||
|
||||
fn create_backend() -> Box<dyn Backend> {
|
||||
|
@ -398,7 +382,7 @@ impl<'a> CanvasData<'a> {
|
|||
size: Size2D<u64>,
|
||||
webrender_api: Box<dyn WebrenderApi>,
|
||||
antialias: AntialiasMode,
|
||||
font_cache_thread: FontCacheThread,
|
||||
font_context: Arc<FontContext<FontCacheThread>>,
|
||||
) -> CanvasData<'a> {
|
||||
let backend = create_backend();
|
||||
let draw_target = backend.create_drawtarget(size);
|
||||
|
@ -412,7 +396,7 @@ impl<'a> CanvasData<'a> {
|
|||
image_key: None,
|
||||
old_image_key: None,
|
||||
very_old_image_key: None,
|
||||
font_cache_thread: Mutex::new(font_cache_thread),
|
||||
font_context,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -494,17 +478,14 @@ impl<'a> CanvasData<'a> {
|
|||
let font = font_style.map_or_else(
|
||||
|| load_system_font_from_style(None),
|
||||
|style| {
|
||||
with_thread_local_font_context(self, |font_context| {
|
||||
let font_group = font_context.font_group(ServoArc::new(style.clone()));
|
||||
let font = font_group
|
||||
.borrow_mut()
|
||||
.first(font_context)
|
||||
.expect("couldn't find font");
|
||||
let font = font.borrow_mut();
|
||||
Font::from_bytes(font.template.data(), 0)
|
||||
.ok()
|
||||
.or_else(|| load_system_font_from_style(Some(style)))
|
||||
})
|
||||
let font_group = self.font_context.font_group(ServoArc::new(style.clone()));
|
||||
let font = font_group
|
||||
.write()
|
||||
.first(&self.font_context)
|
||||
.expect("couldn't find font");
|
||||
Font::from_bytes(font.template.data(), 0)
|
||||
.ok()
|
||||
.or_else(|| load_system_font_from_style(Some(style)))
|
||||
},
|
||||
);
|
||||
let font = match font {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use std::borrow::ToOwned;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use canvas_traits::canvas::*;
|
||||
|
@ -11,6 +12,7 @@ use canvas_traits::ConstellationCanvasMsg;
|
|||
use crossbeam_channel::{select, unbounded, Sender};
|
||||
use euclid::default::Size2D;
|
||||
use gfx::font_cache_thread::FontCacheThread;
|
||||
use gfx::font_context::FontContext;
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
use ipc_channel::router::ROUTER;
|
||||
use log::warn;
|
||||
|
@ -40,7 +42,7 @@ pub struct CanvasPaintThread<'a> {
|
|||
canvases: HashMap<CanvasId, CanvasData<'a>>,
|
||||
next_canvas_id: CanvasId,
|
||||
webrender_api: Box<dyn WebrenderApi>,
|
||||
font_cache_thread: FontCacheThread,
|
||||
font_context: Arc<FontContext<FontCacheThread>>,
|
||||
}
|
||||
|
||||
impl<'a> CanvasPaintThread<'a> {
|
||||
|
@ -52,7 +54,7 @@ impl<'a> CanvasPaintThread<'a> {
|
|||
canvases: HashMap::new(),
|
||||
next_canvas_id: CanvasId(0),
|
||||
webrender_api,
|
||||
font_cache_thread,
|
||||
font_context: Arc::new(FontContext::new(font_cache_thread)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,8 +131,6 @@ impl<'a> CanvasPaintThread<'a> {
|
|||
AntialiasMode::None
|
||||
};
|
||||
|
||||
let font_cache_thread = self.font_cache_thread.clone();
|
||||
|
||||
let canvas_id = self.next_canvas_id;
|
||||
self.next_canvas_id.0 += 1;
|
||||
|
||||
|
@ -138,7 +138,7 @@ impl<'a> CanvasPaintThread<'a> {
|
|||
size,
|
||||
self.webrender_api.clone(),
|
||||
antialias,
|
||||
font_cache_thread,
|
||||
self.font_context.clone(),
|
||||
);
|
||||
self.canvases.insert(canvas_id, canvas_data);
|
||||
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -42,7 +42,7 @@ use style::values::generics::counters::ContentItem;
|
|||
use style::LocalName;
|
||||
|
||||
use crate::block::BlockFlow;
|
||||
use crate::context::{with_thread_local_font_context, LayoutContext};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::data::{InnerLayoutData, LayoutDataFlags};
|
||||
use crate::display_list::items::OpaqueNode;
|
||||
use crate::flex::FlexFlow;
|
||||
|
@ -517,11 +517,10 @@ where
|
|||
// We must scan for runs before computing minimum ascent and descent because scanning
|
||||
// for runs might collapse so much whitespace away that only hypothetical fragments
|
||||
// remain. In that case the inline flow will compute its ascent and descent to be zero.
|
||||
let scanned_fragments =
|
||||
with_thread_local_font_context(self.layout_context, |font_context| {
|
||||
TextRunScanner::new()
|
||||
.scan_for_runs(font_context, mem::take(&mut fragments.fragments))
|
||||
});
|
||||
let scanned_fragments = TextRunScanner::new().scan_for_runs(
|
||||
&self.layout_context.font_context,
|
||||
mem::take(&mut fragments.fragments),
|
||||
);
|
||||
let mut inline_flow_ref = FlowRef::new(Arc::new(InlineFlow::from_fragments(
|
||||
scanned_fragments,
|
||||
node.style(self.style_context()).writing_mode,
|
||||
|
@ -550,11 +549,10 @@ where
|
|||
{
|
||||
// FIXME(#6503): Use Arc::get_mut().unwrap() here.
|
||||
let inline_flow = FlowRef::deref_mut(&mut inline_flow_ref).as_mut_inline();
|
||||
inline_flow.minimum_line_metrics =
|
||||
with_thread_local_font_context(self.layout_context, |font_context| {
|
||||
inline_flow
|
||||
.minimum_line_metrics(font_context, &node.style(self.style_context()))
|
||||
});
|
||||
inline_flow.minimum_line_metrics = inline_flow.minimum_line_metrics(
|
||||
&self.layout_context.font_context,
|
||||
&node.style(self.style_context()),
|
||||
);
|
||||
}
|
||||
|
||||
inline_flow_ref.finish();
|
||||
|
@ -1545,11 +1543,10 @@ where
|
|||
)),
|
||||
self.layout_context,
|
||||
));
|
||||
let marker_fragments =
|
||||
with_thread_local_font_context(self.layout_context, |font_context| {
|
||||
TextRunScanner::new()
|
||||
.scan_for_runs(font_context, unscanned_marker_fragments)
|
||||
});
|
||||
let marker_fragments = TextRunScanner::new().scan_for_runs(
|
||||
&self.layout_context.font_context,
|
||||
unscanned_marker_fragments,
|
||||
);
|
||||
marker_fragments.fragments
|
||||
},
|
||||
ListStyleTypeContent::GeneratedContent(info) => vec![Fragment::new(
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
//! Data needed by layout.
|
||||
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
@ -13,7 +12,6 @@ use std::thread;
|
|||
use fnv::FnvHasher;
|
||||
use gfx::font_cache_thread::FontCacheThread;
|
||||
use gfx::font_context::FontContext;
|
||||
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use net_traits::image_cache::{
|
||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder,
|
||||
|
@ -29,32 +27,6 @@ use crate::display_list::items::{OpaqueNode, WebRenderImageInfo};
|
|||
|
||||
pub type LayoutFontContext = FontContext<FontCacheThread>;
|
||||
|
||||
thread_local!(static FONT_CONTEXT_KEY: RefCell<Option<LayoutFontContext>> = RefCell::new(None));
|
||||
|
||||
pub fn with_thread_local_font_context<F, R>(layout_context: &LayoutContext, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut LayoutFontContext) -> R,
|
||||
{
|
||||
FONT_CONTEXT_KEY.with(|k| {
|
||||
let mut font_context = k.borrow_mut();
|
||||
if font_context.is_none() {
|
||||
let font_cache_thread = layout_context.font_cache_thread.lock().unwrap().clone();
|
||||
*font_context = Some(FontContext::new(font_cache_thread));
|
||||
}
|
||||
f(&mut RefMut::map(font_context, |x| x.as_mut().unwrap()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn malloc_size_of_persistent_local_context(ops: &mut MallocSizeOfOps) -> usize {
|
||||
FONT_CONTEXT_KEY.with(|r| {
|
||||
if let Some(ref context) = *r.borrow() {
|
||||
context.size_of(ops)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type WebrenderImageCache =
|
||||
HashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo, BuildHasherDefault<FnvHasher>>;
|
||||
|
||||
|
@ -72,8 +44,8 @@ pub struct LayoutContext<'a> {
|
|||
/// Reference to the script thread image cache.
|
||||
pub image_cache: Arc<dyn ImageCache>,
|
||||
|
||||
/// Interface to the font cache thread.
|
||||
pub font_cache_thread: Mutex<FontCacheThread>,
|
||||
/// A FontContext to be used during layout.
|
||||
pub font_context: Arc<FontContext<FontCacheThread>>,
|
||||
|
||||
/// A cache of WebRender image info.
|
||||
pub webrender_image_cache: Arc<RwLock<WebrenderImageCache>>,
|
||||
|
|
|
@ -57,7 +57,7 @@ use style::values::generics::transform;
|
|||
use webrender_api::units::LayoutTransform;
|
||||
use webrender_api::{self, ImageKey};
|
||||
|
||||
use crate::context::{with_thread_local_font_context, LayoutContext};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::items::{ClipScrollNodeIndex, OpaqueNode, BLUR_INFLATION_FACTOR};
|
||||
use crate::display_list::ToLayout;
|
||||
use crate::floats::ClearType;
|
||||
|
@ -852,9 +852,8 @@ impl Fragment {
|
|||
))),
|
||||
);
|
||||
unscanned_ellipsis_fragments.push_back(ellipsis_fragment);
|
||||
let ellipsis_fragments = with_thread_local_font_context(layout_context, |font_context| {
|
||||
TextRunScanner::new().scan_for_runs(font_context, unscanned_ellipsis_fragments)
|
||||
});
|
||||
let ellipsis_fragments = TextRunScanner::new()
|
||||
.scan_for_runs(&layout_context.font_context, unscanned_ellipsis_fragments);
|
||||
debug_assert_eq!(ellipsis_fragments.len(), 1);
|
||||
ellipsis_fragment = ellipsis_fragments.fragments.into_iter().next().unwrap();
|
||||
ellipsis_fragment.flags |= FragmentFlags::IS_ELLIPSIS;
|
||||
|
@ -2346,9 +2345,10 @@ impl Fragment {
|
|||
return InlineMetrics::new(Au(0), Au(0), Au(0));
|
||||
}
|
||||
// See CSS 2.1 § 10.8.1.
|
||||
let font_metrics = with_thread_local_font_context(layout_context, |font_context| {
|
||||
text::font_metrics_for_style(font_context, self_.style.clone_font())
|
||||
});
|
||||
let font_metrics = text::font_metrics_for_style(
|
||||
&layout_context.font_context,
|
||||
self_.style.clone_font(),
|
||||
);
|
||||
let line_height = text::line_height_from_style(&self_.style, &font_metrics);
|
||||
InlineMetrics::from_font_metrics(&info.run.font_metrics, line_height)
|
||||
}
|
||||
|
@ -2426,10 +2426,10 @@ impl Fragment {
|
|||
VerticalAlign::Keyword(kw) => match kw {
|
||||
VerticalAlignKeyword::Baseline => {},
|
||||
VerticalAlignKeyword::Middle => {
|
||||
let font_metrics =
|
||||
with_thread_local_font_context(layout_context, |font_context| {
|
||||
text::font_metrics_for_style(font_context, self.style.clone_font())
|
||||
});
|
||||
let font_metrics = text::font_metrics_for_style(
|
||||
&layout_context.font_context,
|
||||
self.style.clone_font(),
|
||||
);
|
||||
offset += (content_inline_metrics.ascent -
|
||||
content_inline_metrics.space_below_baseline -
|
||||
font_metrics.x_height)
|
||||
|
|
|
@ -20,7 +20,7 @@ use style::servo::restyle_damage::ServoRestyleDamage;
|
|||
use style::values::generics::counters::ContentItem;
|
||||
use style::values::specified::list::{QuotePair, Quotes};
|
||||
|
||||
use crate::context::{with_thread_local_font_context, LayoutContext};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::items::OpaqueNode;
|
||||
use crate::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils};
|
||||
use crate::fragment::{
|
||||
|
@ -493,9 +493,7 @@ fn render_text(
|
|||
));
|
||||
// FIXME(pcwalton): This should properly handle multiple marker fragments. This could happen
|
||||
// due to text run splitting.
|
||||
let fragments = with_thread_local_font_context(layout_context, |font_context| {
|
||||
TextRunScanner::new().scan_for_runs(font_context, fragments)
|
||||
});
|
||||
let fragments = TextRunScanner::new().scan_for_runs(&layout_context.font_context, fragments);
|
||||
if fragments.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
|
|
@ -1239,7 +1239,7 @@ impl InlineFlow {
|
|||
/// `style` is the style of the block.
|
||||
pub fn minimum_line_metrics(
|
||||
&self,
|
||||
font_context: &mut LayoutFontContext,
|
||||
font_context: &LayoutFontContext,
|
||||
style: &ComputedValues,
|
||||
) -> LineMetrics {
|
||||
InlineFlow::minimum_line_metrics_for_fragments(
|
||||
|
@ -1255,7 +1255,7 @@ impl InlineFlow {
|
|||
/// `style` is the style of the block that these fragments belong to.
|
||||
pub fn minimum_line_metrics_for_fragments(
|
||||
fragments: &[Fragment],
|
||||
font_context: &mut LayoutFontContext,
|
||||
font_context: &LayoutFontContext,
|
||||
style: &ComputedValues,
|
||||
) -> LineMetrics {
|
||||
// As a special case, if this flow contains only hypothetical fragments, then the entire
|
||||
|
|
|
@ -14,7 +14,7 @@ use style::properties::ComputedValues;
|
|||
use style::servo::restyle_damage::ServoRestyleDamage;
|
||||
|
||||
use crate::block::BlockFlow;
|
||||
use crate::context::{with_thread_local_font_context, LayoutContext};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::display_list::items::DisplayListSection;
|
||||
use crate::display_list::{
|
||||
BorderPaintingMode, DisplayListBuildState, StackingContextCollectionState,
|
||||
|
@ -114,13 +114,11 @@ impl ListItemFlow {
|
|||
|
||||
fn assign_marker_block_sizes(&mut self, layout_context: &LayoutContext) {
|
||||
// FIXME(pcwalton): Do this during flow construction, like `InlineFlow` does?
|
||||
let marker_line_metrics = with_thread_local_font_context(layout_context, |font_context| {
|
||||
InlineFlow::minimum_line_metrics_for_fragments(
|
||||
&self.marker_fragments,
|
||||
font_context,
|
||||
&self.block_flow.fragment.style,
|
||||
)
|
||||
});
|
||||
let marker_line_metrics = InlineFlow::minimum_line_metrics_for_fragments(
|
||||
&self.marker_fragments,
|
||||
&layout_context.font_context,
|
||||
&self.block_flow.fragment.style,
|
||||
);
|
||||
|
||||
for marker in &mut self.marker_fragments {
|
||||
marker.assign_replaced_block_size_if_necessary();
|
||||
|
|
|
@ -70,7 +70,7 @@ impl TextRunScanner {
|
|||
|
||||
pub fn scan_for_runs(
|
||||
&mut self,
|
||||
font_context: &mut LayoutFontContext,
|
||||
font_context: &LayoutFontContext,
|
||||
mut fragments: LinkedList<Fragment>,
|
||||
) -> InlineFragments {
|
||||
debug!(
|
||||
|
@ -150,7 +150,7 @@ impl TextRunScanner {
|
|||
/// be adjusted.
|
||||
fn flush_clump_to_list(
|
||||
&mut self,
|
||||
font_context: &mut LayoutFontContext,
|
||||
font_context: &LayoutFontContext,
|
||||
out_fragments: &mut Vec<Fragment>,
|
||||
paragraph_bytes_processed: &mut usize,
|
||||
bidi_levels: Option<&[bidi::Level]>,
|
||||
|
@ -203,10 +203,9 @@ impl TextRunScanner {
|
|||
.map(|l| l.into())
|
||||
.unwrap_or_else(|| {
|
||||
let space_width = font_group
|
||||
.borrow_mut()
|
||||
.write()
|
||||
.find_by_codepoint(font_context, ' ')
|
||||
.and_then(|font| {
|
||||
let font = font.borrow();
|
||||
font.glyph_index(' ')
|
||||
.map(|glyph_id| font.glyph_h_advance(glyph_id))
|
||||
})
|
||||
|
@ -248,7 +247,7 @@ impl TextRunScanner {
|
|||
for (byte_index, character) in text.char_indices() {
|
||||
if !character.is_control() {
|
||||
let font = font_group
|
||||
.borrow_mut()
|
||||
.write()
|
||||
.find_by_codepoint(font_context, character);
|
||||
|
||||
let bidi_level = match bidi_levels {
|
||||
|
@ -367,7 +366,7 @@ impl TextRunScanner {
|
|||
// If no font is found (including fallbacks), there's no way we can render.
|
||||
let font = match run_info
|
||||
.font
|
||||
.or_else(|| font_group.borrow_mut().first(font_context))
|
||||
.or_else(|| font_group.write().first(font_context))
|
||||
{
|
||||
Some(font) => font,
|
||||
None => {
|
||||
|
@ -377,7 +376,7 @@ impl TextRunScanner {
|
|||
};
|
||||
|
||||
let (run, break_at_zero) = TextRun::new(
|
||||
&mut font.borrow_mut(),
|
||||
font,
|
||||
run_info.text,
|
||||
&options,
|
||||
run_info.bidi_level,
|
||||
|
@ -535,14 +534,12 @@ fn bounding_box_for_run_metrics(
|
|||
/// Panics if no font can be found for the given font style.
|
||||
#[inline]
|
||||
pub fn font_metrics_for_style(
|
||||
font_context: &mut LayoutFontContext,
|
||||
font_context: &LayoutFontContext,
|
||||
style: crate::ServoArc<FontStyleStruct>,
|
||||
) -> FontMetrics {
|
||||
let font_group = font_context.font_group(style);
|
||||
let font = font_group.borrow_mut().first(font_context);
|
||||
let font = font.as_ref().unwrap().borrow();
|
||||
|
||||
font.metrics.clone()
|
||||
let font = font_group.write().first(font_context);
|
||||
font.as_ref().unwrap().metrics.clone()
|
||||
}
|
||||
|
||||
/// Returns the line block-size needed by the given computed style and font size.
|
||||
|
@ -664,10 +661,8 @@ impl RunInfo {
|
|||
|
||||
fn has_font(&self, font: &Option<FontRef>) -> bool {
|
||||
fn identifier_and_pt_size(font: &Option<FontRef>) -> Option<(FontIdentifier, Au)> {
|
||||
font.as_ref().map(|font| {
|
||||
let font = font.borrow();
|
||||
(font.identifier().clone(), font.descriptor.pt_size)
|
||||
})
|
||||
font.as_ref()
|
||||
.map(|font| (font.identifier().clone(), font.descriptor.pt_size))
|
||||
}
|
||||
|
||||
identifier_and_pt_size(&self.font) == identifier_and_pt_size(font)
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
* 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::sync::{Arc, Mutex};
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
|
@ -12,7 +11,7 @@ use msg::constellation_msg::PipelineId;
|
|||
use net_traits::image_cache::{
|
||||
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder,
|
||||
};
|
||||
use parking_lot::{ReentrantMutex, RwLock};
|
||||
use parking_lot::RwLock;
|
||||
use script_layout_interface::{PendingImage, PendingImageState};
|
||||
use servo_url::{ImmutableOrigin, ServoUrl};
|
||||
use style::context::SharedStyleContext;
|
||||
|
@ -20,8 +19,6 @@ use style::dom::OpaqueNode;
|
|||
|
||||
use crate::display_list::WebRenderImageInfo;
|
||||
|
||||
thread_local!(static FONT_CONTEXT: RefCell<Option<FontContext<FontCacheThread>>> = RefCell::new(None));
|
||||
|
||||
pub struct LayoutContext<'a> {
|
||||
pub id: PipelineId,
|
||||
pub use_rayon: bool,
|
||||
|
@ -31,7 +28,7 @@ pub struct LayoutContext<'a> {
|
|||
pub style_context: SharedStyleContext<'a>,
|
||||
|
||||
/// A FontContext to be used during layout.
|
||||
pub font_cache_thread: Arc<ReentrantMutex<FontCacheThread>>,
|
||||
pub font_context: Arc<FontContext<FontCacheThread>>,
|
||||
|
||||
/// Reference to the script thread image cache.
|
||||
pub image_cache: Arc<dyn ImageCache>,
|
||||
|
@ -133,27 +130,4 @@ impl<'a> LayoutContext<'a> {
|
|||
None | Some(ImageOrMetadataAvailable::MetadataAvailable(_)) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_font_context<F, R>(&self, callback: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut FontContext<FontCacheThread>) -> R,
|
||||
{
|
||||
with_thread_local_font_context(&self.font_cache_thread, callback)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_thread_local_font_context<F, R>(
|
||||
font_cache_thread: &ReentrantMutex<FontCacheThread>,
|
||||
callback: F,
|
||||
) -> R
|
||||
where
|
||||
F: FnOnce(&mut FontContext<FontCacheThread>) -> R,
|
||||
{
|
||||
FONT_CONTEXT.with(|font_context| {
|
||||
callback(
|
||||
font_context
|
||||
.borrow_mut()
|
||||
.get_or_insert_with(|| FontContext::new(font_cache_thread.lock().clone())),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1638,10 +1638,9 @@ impl InlineFormattingContext {
|
|||
|
||||
// It's unfortunate that it isn't possible to get this during IFC text processing, but in
|
||||
// that situation the style of the containing block is unknown.
|
||||
let default_font_metrics = layout_context.with_font_context(|font_context| {
|
||||
get_font_for_first_font_for_style(style, font_context)
|
||||
.map(|font| font.borrow().metrics.clone())
|
||||
});
|
||||
let default_font_metrics =
|
||||
get_font_for_first_font_for_style(style, &layout_context.font_context)
|
||||
.map(|font| font.metrics.clone());
|
||||
|
||||
let style_text = containing_block.style.get_inherited_text();
|
||||
let mut ifc = InlineFormattingContextState {
|
||||
|
@ -1768,34 +1767,30 @@ impl InlineFormattingContext {
|
|||
// For the purposes of `text-transform: capitalize` the start of the IFC is a word boundary.
|
||||
let mut on_word_boundary = true;
|
||||
|
||||
layout_context.with_font_context(|font_context| {
|
||||
let mut linebreaker = None;
|
||||
self.foreach(|iter_item| match iter_item {
|
||||
InlineFormattingContextIterItem::Item(InlineLevelBox::TextRun(
|
||||
ref mut text_run,
|
||||
)) => {
|
||||
text_run.break_and_shape(
|
||||
font_context,
|
||||
&mut linebreaker,
|
||||
&mut ifc_fonts,
|
||||
&mut last_inline_box_ended_with_white_space,
|
||||
&mut on_word_boundary,
|
||||
);
|
||||
},
|
||||
InlineFormattingContextIterItem::Item(InlineLevelBox::InlineBox(inline_box)) => {
|
||||
if let Some(font) =
|
||||
get_font_for_first_font_for_style(&inline_box.style, font_context)
|
||||
{
|
||||
inline_box.default_font_index =
|
||||
Some(add_or_get_font(&font, &mut ifc_fonts));
|
||||
}
|
||||
},
|
||||
InlineFormattingContextIterItem::Item(InlineLevelBox::Atomic(_)) => {
|
||||
last_inline_box_ended_with_white_space = false;
|
||||
on_word_boundary = true;
|
||||
},
|
||||
_ => {},
|
||||
});
|
||||
let mut linebreaker = None;
|
||||
self.foreach(|iter_item| match iter_item {
|
||||
InlineFormattingContextIterItem::Item(InlineLevelBox::TextRun(ref mut text_run)) => {
|
||||
text_run.break_and_shape(
|
||||
&layout_context.font_context,
|
||||
&mut linebreaker,
|
||||
&mut ifc_fonts,
|
||||
&mut last_inline_box_ended_with_white_space,
|
||||
&mut on_word_boundary,
|
||||
);
|
||||
},
|
||||
InlineFormattingContextIterItem::Item(InlineLevelBox::InlineBox(inline_box)) => {
|
||||
if let Some(font) = get_font_for_first_font_for_style(
|
||||
&inline_box.style,
|
||||
&layout_context.font_context,
|
||||
) {
|
||||
inline_box.default_font_index = Some(add_or_get_font(&font, &mut ifc_fonts));
|
||||
}
|
||||
},
|
||||
InlineFormattingContextIterItem::Item(InlineLevelBox::Atomic(_)) => {
|
||||
last_inline_box_ended_with_white_space = false;
|
||||
on_word_boundary = true;
|
||||
},
|
||||
_ => {},
|
||||
});
|
||||
|
||||
self.font_metrics = ifc_fonts;
|
||||
|
|
|
@ -111,7 +111,7 @@ impl TextRunSegment {
|
|||
/// compatible with this segment or false otherwise.
|
||||
fn update_if_compatible(
|
||||
&mut self,
|
||||
font: &FontRef,
|
||||
new_font: &FontRef,
|
||||
script: Script,
|
||||
fonts: &[FontKeyAndMetrics],
|
||||
) -> bool {
|
||||
|
@ -120,7 +120,6 @@ impl TextRunSegment {
|
|||
}
|
||||
|
||||
let current_font_key_and_metrics = &fonts[self.font_index];
|
||||
let new_font = font.borrow();
|
||||
if new_font.font_key != current_font_key_and_metrics.key ||
|
||||
new_font.descriptor.pt_size != current_font_key_and_metrics.pt_size
|
||||
{
|
||||
|
@ -208,7 +207,7 @@ impl TextRun {
|
|||
|
||||
pub(super) fn break_and_shape(
|
||||
&mut self,
|
||||
font_context: &mut FontContext<FontCacheThread>,
|
||||
font_context: &FontContext<FontCacheThread>,
|
||||
linebreaker: &mut Option<LineBreakLeafIter>,
|
||||
font_cache: &mut Vec<FontKeyAndMetrics>,
|
||||
last_inline_box_ended_with_collapsible_white_space: &mut bool,
|
||||
|
@ -244,7 +243,6 @@ impl TextRun {
|
|||
let segments = segment_results
|
||||
.into_iter()
|
||||
.map(|(mut segment, font)| {
|
||||
let mut font = font.borrow_mut();
|
||||
let word_spacing = style_word_spacing.unwrap_or_else(|| {
|
||||
let space_width = font
|
||||
.glyph_index(' ')
|
||||
|
@ -260,7 +258,7 @@ impl TextRun {
|
|||
};
|
||||
(segment.runs, segment.break_at_start) =
|
||||
gfx::text::text_run::TextRun::break_and_shape(
|
||||
&mut font,
|
||||
font,
|
||||
&self.text
|
||||
[segment.range.begin().0 as usize..segment.range.end().0 as usize],
|
||||
&shaping_options,
|
||||
|
@ -280,7 +278,7 @@ impl TextRun {
|
|||
/// [`super::InlineFormattingContext`].
|
||||
fn segment_text(
|
||||
&mut self,
|
||||
font_context: &mut FontContext<FontCacheThread>,
|
||||
font_context: &FontContext<FontCacheThread>,
|
||||
font_cache: &mut Vec<FontKeyAndMetrics>,
|
||||
last_inline_box_ended_with_collapsible_white_space: &mut bool,
|
||||
on_word_boundary: &mut bool,
|
||||
|
@ -341,7 +339,7 @@ impl TextRun {
|
|||
}
|
||||
|
||||
let font = match font_group
|
||||
.borrow_mut()
|
||||
.write()
|
||||
.find_by_codepoint(font_context, character)
|
||||
{
|
||||
Some(font) => font,
|
||||
|
@ -383,7 +381,7 @@ impl TextRun {
|
|||
// Either we have a current segment or we only had control character and whitespace. In both
|
||||
// of those cases, just use the first font.
|
||||
if current.is_none() {
|
||||
current = font_group.borrow_mut().first(font_context).map(|font| {
|
||||
current = font_group.write().first(font_context).map(|font| {
|
||||
let font_index = add_or_get_font(&font, font_cache);
|
||||
(
|
||||
TextRunSegment::new(font_index, Script::Common, ByteIndex(0)),
|
||||
|
@ -489,7 +487,6 @@ fn char_does_not_change_font(character: char) -> bool {
|
|||
}
|
||||
|
||||
pub(super) fn add_or_get_font(font: &FontRef, ifc_fonts: &mut Vec<FontKeyAndMetrics>) -> usize {
|
||||
let font = font.borrow();
|
||||
for (index, ifc_font_info) in ifc_fonts.iter().enumerate() {
|
||||
if ifc_font_info.key == font.font_key && ifc_font_info.pt_size == font.descriptor.pt_size {
|
||||
return index;
|
||||
|
@ -505,11 +502,11 @@ pub(super) fn add_or_get_font(font: &FontRef, ifc_fonts: &mut Vec<FontKeyAndMetr
|
|||
|
||||
pub(super) fn get_font_for_first_font_for_style(
|
||||
style: &ComputedValues,
|
||||
font_context: &mut FontContext<FontCacheThread>,
|
||||
font_context: &FontContext<FontCacheThread>,
|
||||
) -> Option<FontRef> {
|
||||
let font = font_context
|
||||
.font_group(style.clone_font())
|
||||
.borrow_mut()
|
||||
.write()
|
||||
.first(font_context);
|
||||
if font.is_none() {
|
||||
warn!("Could not find font for style: {:?}", style.clone_font());
|
||||
|
|
|
@ -22,16 +22,15 @@ use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect, Size2D as
|
|||
use euclid::{Point2D, Rect, Scale, Size2D};
|
||||
use fnv::FnvHashMap;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use gfx::font;
|
||||
use gfx::font_cache_thread::FontCacheThread;
|
||||
use gfx::{font, font_context};
|
||||
use gfx::font_context::FontContext;
|
||||
use gfx_traits::{node_id_from_scroll_id, Epoch};
|
||||
use histogram::Histogram;
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
use ipc_channel::router::ROUTER;
|
||||
use layout::construct::ConstructionResult;
|
||||
use layout::context::{
|
||||
malloc_size_of_persistent_local_context, LayoutContext, RegisteredPainter, RegisteredPainters,
|
||||
};
|
||||
use layout::context::{LayoutContext, RegisteredPainter, RegisteredPainters};
|
||||
use layout::display_list::items::{DisplayList, ScrollOffsetMap, WebRenderImageInfo};
|
||||
use layout::display_list::{IndexableText, ToLayout};
|
||||
use layout::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils, MutableOwnedFlowUtils};
|
||||
|
@ -136,6 +135,9 @@ pub struct LayoutThread {
|
|||
/// Public interface to the font cache thread.
|
||||
font_cache_thread: FontCacheThread,
|
||||
|
||||
/// A FontContext to be used during layout.
|
||||
font_context: Arc<FontContext<FontCacheThread>>,
|
||||
|
||||
/// Is this the first reflow in this LayoutThread?
|
||||
first_reflow: Cell<bool>,
|
||||
|
||||
|
@ -513,7 +515,7 @@ impl Layout for LayoutThread {
|
|||
// malloc_enclosing_size_of function.
|
||||
let mut ops = MallocSizeOfOps::new(servo_allocator::usable_size, None, None);
|
||||
|
||||
// FIXME(njn): Just measuring the display tree for now.
|
||||
// TODO: Measure more than just display list, stylist, and font context.
|
||||
let display_list = self.display_list.borrow();
|
||||
let display_list_ref = display_list.as_ref();
|
||||
let formatted_url = &format!("url({})", self.url);
|
||||
|
@ -528,13 +530,6 @@ impl Layout for LayoutThread {
|
|||
kind: ReportKind::ExplicitJemallocHeapSize,
|
||||
size: self.stylist.size_of(&mut ops),
|
||||
});
|
||||
|
||||
// The LayoutThread has data in Persistent TLS...
|
||||
reports.push(Report {
|
||||
path: path![formatted_url, "layout-thread", "local-context"],
|
||||
kind: ReportKind::ExplicitJemallocHeapSize,
|
||||
size: malloc_size_of_persistent_local_context(&mut ops),
|
||||
});
|
||||
}
|
||||
|
||||
fn reflow(&mut self, script_reflow: script_layout_interface::ScriptReflow) {
|
||||
|
@ -573,6 +568,7 @@ impl LayoutThread {
|
|||
// Let webrender know about this pipeline by sending an empty display list.
|
||||
webrender_api.send_initial_transaction(id.into());
|
||||
|
||||
let font_context = Arc::new(FontContext::new(font_cache_thread.clone()));
|
||||
let device = Device::new(
|
||||
MediaType::screen(),
|
||||
QuirksMode::NoQuirks,
|
||||
|
@ -602,6 +598,7 @@ impl LayoutThread {
|
|||
registered_painters: RegisteredPaintersImpl(Default::default()),
|
||||
image_cache,
|
||||
font_cache_thread,
|
||||
font_context,
|
||||
first_reflow: Cell::new(true),
|
||||
font_cache_sender: ipc_font_cache_sender,
|
||||
parallel_flag: true,
|
||||
|
@ -675,7 +672,7 @@ impl LayoutThread {
|
|||
traversal_flags,
|
||||
),
|
||||
image_cache: self.image_cache.clone(),
|
||||
font_cache_thread: Mutex::new(self.font_cache_thread.clone()),
|
||||
font_context: self.font_context.clone(),
|
||||
webrender_image_cache: self.webrender_image_cache.clone(),
|
||||
pending_images: Mutex::new(vec![]),
|
||||
registered_painters: &self.registered_painters,
|
||||
|
@ -726,7 +723,7 @@ impl LayoutThread {
|
|||
}
|
||||
|
||||
fn handle_web_font_loaded(&self) {
|
||||
font_context::invalidate_font_caches();
|
||||
self.font_context.invalidate_caches();
|
||||
self.script_chan
|
||||
.send(ConstellationControlMsg::WebFontLoaded(self.id))
|
||||
.unwrap();
|
||||
|
|
|
@ -22,7 +22,7 @@ use euclid::{Point2D, Scale, Size2D, Vector2D};
|
|||
use fnv::FnvHashMap;
|
||||
use fxhash::FxHashMap;
|
||||
use gfx::font_cache_thread::FontCacheThread;
|
||||
use gfx::font_context;
|
||||
use gfx::font_context::FontContext;
|
||||
use gfx_traits::{node_id_from_scroll_id, Epoch};
|
||||
use ipc_channel::ipc::{self, IpcSender};
|
||||
use ipc_channel::router::ROUTER;
|
||||
|
@ -41,7 +41,7 @@ use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
|
|||
use metrics::{PaintTimeMetrics, ProfilerMetadataFactory};
|
||||
use msg::constellation_msg::{BrowsingContextId, PipelineId};
|
||||
use net_traits::image_cache::{ImageCache, UsePlaceholder};
|
||||
use parking_lot::{ReentrantMutex, RwLock};
|
||||
use parking_lot::RwLock;
|
||||
use profile_traits::mem::{Report, ReportKind};
|
||||
use profile_traits::path;
|
||||
use profile_traits::time::{
|
||||
|
@ -121,7 +121,10 @@ pub struct LayoutThread {
|
|||
|
||||
/// Public interface to the font cache thread. This needs to be behind a [`ReentrantMutex`],
|
||||
/// because some font cache operations can trigger others.
|
||||
font_cache_thread: Arc<ReentrantMutex<FontCacheThread>>,
|
||||
font_cache_thread: FontCacheThread,
|
||||
|
||||
/// A FontContext to be used during layout.
|
||||
font_context: Arc<FontContext<FontCacheThread>>,
|
||||
|
||||
/// Is this the first reflow in this LayoutThread?
|
||||
first_reflow: Cell<bool>,
|
||||
|
@ -484,13 +487,13 @@ impl LayoutThread {
|
|||
|
||||
// The device pixel ratio is incorrect (it does not have the hidpi value),
|
||||
// but it will be set correctly when the initial reflow takes place.
|
||||
let font_cache_thread = Arc::new(ReentrantMutex::new(font_cache_thread));
|
||||
let font_context = Arc::new(FontContext::new(font_cache_thread.clone()));
|
||||
let device = Device::new(
|
||||
MediaType::screen(),
|
||||
QuirksMode::NoQuirks,
|
||||
window_size.initial_viewport,
|
||||
window_size.device_pixel_ratio,
|
||||
Box::new(LayoutFontMetricsProvider(font_cache_thread.clone())),
|
||||
Box::new(LayoutFontMetricsProvider(font_context.clone())),
|
||||
);
|
||||
|
||||
// Ask the router to proxy IPC messages from the font cache thread to layout.
|
||||
|
@ -514,6 +517,7 @@ impl LayoutThread {
|
|||
registered_painters: RegisteredPaintersImpl(Default::default()),
|
||||
image_cache,
|
||||
font_cache_thread,
|
||||
font_context,
|
||||
first_reflow: Cell::new(true),
|
||||
font_cache_sender: ipc_font_cache_sender,
|
||||
generation: Cell::new(0),
|
||||
|
@ -584,7 +588,7 @@ impl LayoutThread {
|
|||
traversal_flags,
|
||||
),
|
||||
image_cache: self.image_cache.clone(),
|
||||
font_cache_thread: self.font_cache_thread.clone(),
|
||||
font_context: self.font_context.clone(),
|
||||
webrender_image_cache: self.webrender_image_cache.clone(),
|
||||
pending_images: Mutex::new(vec![]),
|
||||
use_rayon,
|
||||
|
@ -616,10 +620,8 @@ impl LayoutThread {
|
|||
// Find all font-face rules and notify the font cache of them.
|
||||
// GWTODO: Need to handle unloading web fonts.
|
||||
if stylesheet.is_effective_for_device(self.stylist.device(), guard) {
|
||||
let newly_loading_font_count = self
|
||||
.font_cache_thread
|
||||
.lock()
|
||||
.add_all_web_fonts_from_stylesheet(
|
||||
let newly_loading_font_count =
|
||||
self.font_cache_thread.add_all_web_fonts_from_stylesheet(
|
||||
stylesheet,
|
||||
guard,
|
||||
self.stylist.device(),
|
||||
|
@ -637,7 +639,7 @@ impl LayoutThread {
|
|||
}
|
||||
|
||||
fn handle_web_font_loaded(&self) {
|
||||
font_context::invalidate_font_caches();
|
||||
self.font_context.invalidate_caches();
|
||||
self.script_chan
|
||||
.send(ConstellationControlMsg::WebFontLoaded(self.id))
|
||||
.unwrap();
|
||||
|
@ -1076,7 +1078,7 @@ impl LayoutThread {
|
|||
self.stylist.quirks_mode(),
|
||||
window_size_data.initial_viewport,
|
||||
window_size_data.device_pixel_ratio,
|
||||
Box::new(LayoutFontMetricsProvider(self.font_cache_thread.clone())),
|
||||
Box::new(LayoutFontMetricsProvider(self.font_context.clone())),
|
||||
);
|
||||
|
||||
// Preserve any previously computed root font size.
|
||||
|
@ -1235,7 +1237,7 @@ impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct LayoutFontMetricsProvider(Arc<ReentrantMutex<FontCacheThread>>);
|
||||
struct LayoutFontMetricsProvider(Arc<FontContext<FontCacheThread>>);
|
||||
|
||||
impl FontMetricsProvider for LayoutFontMetricsProvider {
|
||||
fn query_font_metrics(
|
||||
|
@ -1246,55 +1248,55 @@ impl FontMetricsProvider for LayoutFontMetricsProvider {
|
|||
_in_media_query: bool,
|
||||
_retrieve_math_scales: bool,
|
||||
) -> FontMetrics {
|
||||
layout::context::with_thread_local_font_context(&self.0, move |font_context| {
|
||||
let font_group =
|
||||
font_context.font_group_with_size(ServoArc::new(font.clone()), base_size.into());
|
||||
let Some(first_font_metrics) = font_group
|
||||
.borrow_mut()
|
||||
.first(font_context)
|
||||
.map(|font| font.borrow().metrics.clone())
|
||||
else {
|
||||
return Default::default();
|
||||
};
|
||||
let font_context = &self.0;
|
||||
let font_group = self
|
||||
.0
|
||||
.font_group_with_size(ServoArc::new(font.clone()), base_size.into());
|
||||
|
||||
// Only use the x-height of this font if it is non-zero. Some fonts return
|
||||
// inaccurate metrics, which shouldn't be used.
|
||||
let x_height = Some(first_font_metrics.x_height)
|
||||
.filter(|x_height| !x_height.is_zero())
|
||||
.map(CSSPixelLength::from);
|
||||
let Some(first_font_metrics) = font_group
|
||||
.write()
|
||||
.first(font_context)
|
||||
.map(|font| font.metrics.clone())
|
||||
else {
|
||||
return Default::default();
|
||||
};
|
||||
|
||||
let zero_advance_measure = first_font_metrics
|
||||
.zero_horizontal_advance
|
||||
.or_else(|| {
|
||||
font_group
|
||||
.borrow_mut()
|
||||
.find_by_codepoint(font_context, '0')?
|
||||
.borrow()
|
||||
.metrics
|
||||
.zero_horizontal_advance
|
||||
})
|
||||
.map(CSSPixelLength::from);
|
||||
let ic_width = first_font_metrics
|
||||
.ic_horizontal_advance
|
||||
.or_else(|| {
|
||||
font_group
|
||||
.borrow_mut()
|
||||
.find_by_codepoint(font_context, '\u{6C34}')?
|
||||
.borrow()
|
||||
.metrics
|
||||
.ic_horizontal_advance
|
||||
})
|
||||
.map(CSSPixelLength::from);
|
||||
// Only use the x-height of this font if it is non-zero. Some fonts return
|
||||
// inaccurate metrics, which shouldn't be used.
|
||||
let x_height = Some(first_font_metrics.x_height)
|
||||
.filter(|x_height| !x_height.is_zero())
|
||||
.map(CSSPixelLength::from);
|
||||
|
||||
FontMetrics {
|
||||
x_height,
|
||||
zero_advance_measure,
|
||||
cap_height: None,
|
||||
ic_width,
|
||||
ascent: first_font_metrics.ascent.into(),
|
||||
script_percent_scale_down: None,
|
||||
script_script_percent_scale_down: None,
|
||||
}
|
||||
})
|
||||
let zero_advance_measure = first_font_metrics
|
||||
.zero_horizontal_advance
|
||||
.or_else(|| {
|
||||
font_group
|
||||
.write()
|
||||
.find_by_codepoint(font_context, '0')?
|
||||
.metrics
|
||||
.zero_horizontal_advance
|
||||
})
|
||||
.map(CSSPixelLength::from);
|
||||
|
||||
let ic_width = first_font_metrics
|
||||
.ic_horizontal_advance
|
||||
.or_else(|| {
|
||||
font_group
|
||||
.write()
|
||||
.find_by_codepoint(font_context, '\u{6C34}')?
|
||||
.metrics
|
||||
.ic_horizontal_advance
|
||||
})
|
||||
.map(CSSPixelLength::from);
|
||||
|
||||
FontMetrics {
|
||||
x_height,
|
||||
zero_advance_measure,
|
||||
cap_height: None,
|
||||
ic_width,
|
||||
ascent: first_font_metrics.ascent.into(),
|
||||
script_percent_scale_down: None,
|
||||
script_script_percent_scale_down: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue