aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2024-07-11 06:25:38 +0200
committerGitHub <noreply@github.com>2024-07-11 04:25:38 +0000
commit4907e896560dd68bfcd9318b4493de10d7ceee19 (patch)
tree9af64c74ca7fcd240bba12e6cd8841db31ddfccb
parentc6cb7ee98169ce1acb3b43b5071385d8f4f4adc2 (diff)
downloadservo-4907e896560dd68bfcd9318b4493de10d7ceee19.tar.gz
servo-4907e896560dd68bfcd9318b4493de10d7ceee19.zip
canvas: Remove as much usage of `font-kit` as possible (#32758)
Do font selection using Servo's font backend, which is shared with the rest of layout. In addition, delay the creation of the `font-kit` font until just before rendering with `raqote`. The idea is that when `raqote` is no longer used, we can drop the `font-kit` dependency. This change has the side-effect of fixing text rendering in canvas, adding support for font fallback in canvas, and also correcting a bug in font selection with size overrides. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
-rw-r--r--Cargo.lock4
-rw-r--r--components/canvas/Cargo.toml4
-rw-r--r--components/canvas/canvas_data.rs325
-rw-r--r--components/canvas/raqote_backend.rs105
-rw-r--r--components/fonts/font.rs4
-rw-r--r--components/fonts/font_context.rs6
6 files changed, 250 insertions, 198 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 36f9d03f1f0..f8d71891b73 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -626,6 +626,7 @@ dependencies = [
name = "canvas"
version = "0.0.1"
dependencies = [
+ "app_units",
"bitflags 2.6.0",
"byteorder",
"canvas_traits",
@@ -641,8 +642,10 @@ dependencies = [
"lyon_geom",
"net_traits",
"num-traits",
+ "parking_lot",
"pathfinder_geometry",
"pixels",
+ "range",
"raqote",
"servo_arc",
"sparkle",
@@ -650,6 +653,7 @@ dependencies = [
"style_traits",
"surfman",
"time 0.1.45",
+ "unicode-script",
"webrender",
"webrender_api",
"webrender_traits",
diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml
index 80ac1944567..ff0b28af0cb 100644
--- a/components/canvas/Cargo.toml
+++ b/components/canvas/Cargo.toml
@@ -15,6 +15,7 @@ webgl_backtrace = ["canvas_traits/webgl_backtrace"]
xr-profile = ["webxr-api/profile", "time"]
[dependencies]
+app_units = { workspace = true }
bitflags = { workspace = true }
byteorder = { workspace = true }
canvas_traits = { workspace = true }
@@ -30,8 +31,10 @@ log = { workspace = true }
lyon_geom = "1.0.4"
net_traits = { workspace = true }
num-traits = { workspace = true }
+parking_lot = { workspace = true }
pathfinder_geometry = "0.5"
pixels = { path = "../pixels" }
+range = { path = "../range" }
raqote = "0.8.4"
servo_arc = { workspace = true }
sparkle = { workspace = true }
@@ -39,6 +42,7 @@ style = { workspace = true }
style_traits = { workspace = true }
surfman = { workspace = true }
time = { workspace = true, optional = true }
+unicode-script = { workspace = true }
webrender = { workspace = true }
webrender_api = { workspace = true }
webrender_traits = { workspace = true }
diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs
index b7d878ef35f..68adda8883b 100644
--- a/components/canvas/canvas_data.rs
+++ b/components/canvas/canvas_data.rs
@@ -5,23 +5,21 @@
use std::mem;
use std::sync::Arc;
+use app_units::Au;
use canvas_traits::canvas::*;
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
-use euclid::{point2, vec2};
-use font_kit::family_name::FamilyName;
-use font_kit::font::Font;
-use font_kit::metrics::Metrics;
-use font_kit::properties::{Properties, Stretch, Style, Weight};
-use font_kit::source::SystemSource;
-use fonts::{FontCacheThread, FontContext, FontTemplateRefMethods};
+use euclid::point2;
+use fonts::{
+ FontCacheThread, FontContext, FontMetrics, FontRef, GlyphStore, ShapingFlags, ShapingOptions,
+ LAST_RESORT_GLYPH_ADVANCE,
+};
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
-use log::{debug, error, warn};
+use log::{debug, warn};
use num_traits::ToPrimitive;
use servo_arc::Arc as ServoArc;
use style::color::AbsoluteColor;
use style::properties::style_structs::Font as FontStyleStruct;
-use style::values::computed::font;
-use style_traits::values::ToCss;
+use unicode_script::Script;
use webrender_api::units::{DeviceIntSize, RectExt as RectExt_};
use webrender_api::{ImageData, ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey};
use webrender_traits::ImageUpdate;
@@ -232,10 +230,55 @@ impl<'a> PathBuilderRef<'a> {
}
}
-// TODO(pylbrecht)
-// This defines required methods for DrawTarget of azure and raqote
-// The prototypes are derived from azure's methods.
-// TODO: De-abstract now that Azure is removed?
+#[derive(Debug, Default)]
+struct UnshapedTextRun<'a> {
+ font: Option<FontRef>,
+ script: Script,
+ string: &'a str,
+}
+
+impl<'a> UnshapedTextRun<'a> {
+ fn script_and_font_compatible(&self, script: Script, other_font: &Option<FontRef>) -> bool {
+ if self.script != script {
+ return false;
+ }
+
+ match (&self.font, other_font) {
+ (Some(font_a), Some(font_b)) => font_a.identifier() == font_b.identifier(),
+ (None, None) => true,
+ _ => false,
+ }
+ }
+
+ fn to_shaped_text_run(self) -> Option<TextRun> {
+ let font = self.font?;
+ if self.string.is_empty() {
+ return None;
+ }
+
+ let word_spacing = Au::from_f64_px(
+ font.glyph_index(' ')
+ .map(|glyph_id| font.glyph_h_advance(glyph_id))
+ .unwrap_or(LAST_RESORT_GLYPH_ADVANCE),
+ );
+ let options = ShapingOptions {
+ letter_spacing: None,
+ word_spacing,
+ script: self.script,
+ flags: ShapingFlags::empty(),
+ };
+ let glyphs = font.shape_text(self.string, &options);
+ Some(TextRun { font, glyphs })
+ }
+}
+
+pub struct TextRun {
+ pub font: FontRef,
+ pub glyphs: Arc<GlyphStore>,
+}
+
+// This defines required methods for a DrawTarget (currently only implemented for raqote). The
+// prototypes are derived from the now-removed Azure backend's methods.
pub trait GenericDrawTarget {
fn clear_rect(&mut self, rect: &Rect<f32>);
fn copy_surface(
@@ -268,9 +311,7 @@ pub trait GenericDrawTarget {
fn fill(&mut self, path: &Path, pattern: Pattern, draw_options: &DrawOptions);
fn fill_text(
&mut self,
- font: &Font,
- point_size: f32,
- text: &str,
+ text_runs: Vec<TextRun>,
start: Point2D<f32>,
pattern: &Pattern,
draw_options: &DrawOptions,
@@ -455,86 +496,134 @@ impl<'a> CanvasData<'a> {
}
}
- // https://html.spec.whatwg.org/multipage/#text-preparation-algorithm
- pub fn fill_text(
+ pub fn fill_text_with_size(
&mut self,
text: String,
x: f64,
y: f64,
max_width: Option<f64>,
is_rtl: bool,
+ size: f64,
) {
- // Step 2.
+ // > Step 2: Replace all ASCII whitespace in text with U+0020 SPACE characters.
let text = replace_ascii_whitespace(text);
- // Step 3.
- let point_size = self
- .state
- .font_style
- .as_ref()
- .map_or(10., |style| style.font_size.computed_size().px());
- let font_style = self.state.font_style.as_ref();
- let font = font_style.map_or_else(
- || load_system_font_from_style(None),
- |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 {
- Some(f) => f,
- None => {
- error!("Couldn't load desired font or system fallback.");
- return;
- },
+ // > Step 3: Let font be the current font of target, as given by that object's font
+ // > attribute.
+ let Some(ref font_style) = self.state.font_style else {
+ return;
};
- let font_width = font_width(&text, point_size, &font);
-
- // Step 6.
- let max_width = max_width.map(|width| width as f32);
- let (width, scale_factor) = match max_width {
- Some(max_width) if max_width > font_width => (max_width, 1.),
- Some(max_width) => (font_width, max_width / font_width),
- None => (font_width, 1.),
+
+ let font_group = self
+ .font_context
+ .font_group_with_size(font_style.clone(), Au::from_f64_px(size));
+ let mut font_group = font_group.write();
+ let Some(first_font) = font_group.first(&self.font_context) else {
+ warn!("Could not render canvas text, because there was no first font.");
+ return;
};
- // Step 7.
- let start = self.text_origin(x as f32, y as f32, &font.metrics(), width, is_rtl);
+ let mut runs = Vec::new();
+ let mut current_text_run = UnshapedTextRun::default();
+ let mut current_text_run_start_index = 0;
+ for (index, character) in text.char_indices() {
+ // TODO: This should ultimately handle emoji variation selectors, but raqote does not yet
+ // have support for color glyphs.
+ let script = Script::from(character);
+ let font = font_group.find_by_codepoint(&self.font_context, character, None);
+
+ if !current_text_run.script_and_font_compatible(script, &font) {
+ let previous_text_run = mem::replace(
+ &mut current_text_run,
+ UnshapedTextRun {
+ font: font.clone(),
+ script,
+ ..Default::default()
+ },
+ );
+ current_text_run_start_index = index;
+ runs.push(previous_text_run)
+ }
- // TODO: Bidi text layout
+ current_text_run.string =
+ &text[current_text_run_start_index..index + character.len_utf8()];
+ }
+ runs.push(current_text_run);
+
+ // TODO: This doesn't do any kind of line layout at all. In particular, there needs
+ // to be some alignment along a baseline and also support for bidi text.
+ let shaped_runs: Vec<_> = runs
+ .into_iter()
+ .filter_map(UnshapedTextRun::to_shaped_text_run)
+ .collect();
+ let total_advance = shaped_runs
+ .iter()
+ .map(|run| run.glyphs.total_advance())
+ .sum::<Au>()
+ .to_f64_px();
+
+ // > Step 6: If maxWidth was provided and the hypothetical width of the inline box in the
+ // > hypothetical line box is greater than maxWidth CSS pixels, then change font to have a
+ // > more condensed font (if one is available or if a reasonably readable one can be
+ // > synthesized by applying a horizontal scale factor to the font) or a smaller font, and
+ // > return to the previous step.
+ //
+ // TODO: We only try decreasing the font size here. Eventually it would make sense to use
+ // other methods to try to decrease the size, such as finding a narrower font or decreasing
+ // spacing.
+ if let Some(max_width) = max_width {
+ let new_size = (max_width / total_advance * size).floor().max(5.);
+ if total_advance > max_width && new_size != size {
+ self.fill_text_with_size(text, x, y, Some(max_width), is_rtl, new_size);
+ return;
+ }
+ }
- let old_transform = self.get_transform();
- self.set_transform(
- &old_transform
- .pre_translate(vec2(start.x, 0.))
- .pre_scale(scale_factor, 1.)
- .pre_translate(vec2(-start.x, 0.)),
+ // > Step 7: Find the anchor point for the line of text.
+ let start = self.find_anchor_point_for_line_of_text(
+ x as f32,
+ y as f32,
+ &first_font.metrics,
+ total_advance as f32,
+ is_rtl,
);
- // Step 8.
+ // > Step 8: Let result be an array constructed by iterating over each glyph in the inline box
+ // > from left to right (if any), adding to the array, for each glyph, the shape of the glyph
+ // > as it is in the inline box, positioned on a coordinate space using CSS pixels with its
+ // > origin is at the anchor point.
self.drawtarget.fill_text(
- &font,
- point_size,
- &text,
+ shaped_runs,
start,
&self.state.fill_style,
&self.state.draw_options,
);
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#text-preparation-algorithm>
+ pub fn fill_text(
+ &mut self,
+ text: String,
+ x: f64,
+ y: f64,
+ max_width: Option<f64>,
+ is_rtl: bool,
+ ) {
+ let Some(ref font_style) = self.state.font_style else {
+ return;
+ };
- self.set_transform(&old_transform);
+ let size = font_style.font_size.computed_size();
+ self.fill_text_with_size(text, x, y, max_width, is_rtl, size.px() as f64);
}
- fn text_origin(
+ /// Find the *anchor_point* for the given parameters of a line of text.
+ /// See <https://html.spec.whatwg.org/multipage/#text-preparation-algorithm>.
+ fn find_anchor_point_for_line_of_text(
&self,
x: f32,
y: f32,
- metrics: &Metrics,
+ metrics: &FontMetrics,
width: f32,
is_rtl: bool,
) -> Point2D<f32> {
@@ -551,13 +640,15 @@ impl<'a> CanvasData<'a> {
_ => 0.,
};
+ let ascent = metrics.ascent.to_f32_px();
+ let descent = metrics.descent.to_f32_px();
let anchor_y = match self.state.text_baseline {
- TextBaseline::Top => metrics.ascent,
- TextBaseline::Hanging => metrics.ascent * HANGING_BASELINE_DEFAULT,
- TextBaseline::Ideographic => -metrics.descent * IDEOGRAPHIC_BASELINE_DEFAULT,
- TextBaseline::Middle => (metrics.ascent - metrics.descent) / 2.,
+ TextBaseline::Top => ascent,
+ TextBaseline::Hanging => ascent * HANGING_BASELINE_DEFAULT,
+ TextBaseline::Ideographic => -descent * IDEOGRAPHIC_BASELINE_DEFAULT,
+ TextBaseline::Middle => (ascent - descent) / 2.,
TextBaseline::Alphabetic => 0.,
- TextBaseline::Bottom => -metrics.descent,
+ TextBaseline::Bottom => -descent,
};
point2(x + anchor_x, y + anchor_y)
@@ -1140,7 +1231,7 @@ impl<'a> CanvasData<'a> {
}
pub fn set_font(&mut self, font_style: FontStyleStruct) {
- self.state.font_style = Some(font_style)
+ self.state.font_style = Some(ServoArc::new(font_style))
}
pub fn set_text_align(&mut self, text_align: TextAlign) {
@@ -1239,7 +1330,7 @@ pub struct CanvasPaintState<'a> {
pub shadow_offset_y: f64,
pub shadow_blur: f64,
pub shadow_color: Color,
- pub font_style: Option<FontStyleStruct>,
+ pub font_style: Option<ServoArc<FontStyleStruct>>,
pub text_align: TextAlign,
pub text_baseline: TextBaseline,
}
@@ -1330,71 +1421,6 @@ impl RectExt for Rect<u32> {
}
}
-fn to_font_kit_family(font_family: &font::SingleFontFamily) -> FamilyName {
- match font_family {
- font::SingleFontFamily::FamilyName(family_name) => {
- FamilyName::Title(family_name.to_css_string())
- },
- font::SingleFontFamily::Generic(generic) => match generic {
- font::GenericFontFamily::Serif => FamilyName::Serif,
- font::GenericFontFamily::SansSerif => FamilyName::SansSerif,
- font::GenericFontFamily::Monospace => FamilyName::Monospace,
- font::GenericFontFamily::Fantasy => FamilyName::Fantasy,
- font::GenericFontFamily::Cursive => FamilyName::Cursive,
- // TODO: There is no FontFamily::SystemUi.
- font::GenericFontFamily::SystemUi => unreachable!("system-ui should be disabled"),
- font::GenericFontFamily::None => unreachable!("Shouldn't appear in computed values"),
- },
- }
-}
-
-fn load_system_font_from_style(font_style: Option<&FontStyleStruct>) -> Option<Font> {
- let mut properties = Properties::new();
- let style = match font_style {
- Some(style) => style,
- None => return load_default_system_fallback_font(&properties),
- };
- let family_names = style
- .font_family
- .families
- .iter()
- .map(to_font_kit_family)
- .collect::<Vec<_>>();
- let properties = properties
- .style(match style.font_style {
- font::FontStyle::NORMAL => Style::Normal,
- font::FontStyle::ITALIC => Style::Italic,
- _ => {
- // TODO: support oblique angle.
- Style::Oblique
- },
- })
- .weight(Weight(style.font_weight.value()))
- .stretch(Stretch(style.font_stretch.to_percentage().0));
- let font_handle = match SystemSource::new().select_best_match(&family_names, properties) {
- Ok(handle) => handle,
- Err(e) => {
- error!("error getting font handle for style {:?}: {}", style, e);
- return load_default_system_fallback_font(properties);
- },
- };
- match font_handle.load() {
- Ok(f) => Some(f),
- Err(e) => {
- error!("error loading font for style {:?}: {}", style, e);
- load_default_system_fallback_font(properties)
- },
- }
-}
-
-fn load_default_system_fallback_font(properties: &Properties) -> Option<Font> {
- SystemSource::new()
- .select_best_match(&[FamilyName::SansSerif], properties)
- .ok()?
- .load()
- .ok()
-}
-
fn replace_ascii_whitespace(text: String) -> String {
text.chars()
.map(|c| match c {
@@ -1403,18 +1429,3 @@ fn replace_ascii_whitespace(text: String) -> String {
})
.collect()
}
-
-// TODO: This currently calculates the width using just advances and doesn't
-// determine the fallback font in case a character glyph isn't found.
-fn font_width(text: &str, point_size: f32, font: &Font) -> f32 {
- let metrics = font.metrics();
- let mut width = 0.;
- for c in text.chars() {
- if let Some(glyph_id) = font.glyph_for_char(c) {
- if let Ok(advance) = font.advance(glyph_id) {
- width += advance.x() * point_size / metrics.units_per_em as f32;
- }
- }
- }
- width
-}
diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs
index 8a9223bb191..359b0271368 100644
--- a/components/canvas/raqote_backend.rs
+++ b/components/canvas/raqote_backend.rs
@@ -2,23 +2,36 @@
* 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 canvas_traits::canvas::*;
use cssparser::color::clamp_unit_f32;
use euclid::default::{Point2D, Rect, Size2D, Transform2D, Vector2D};
use euclid::Angle;
use font_kit::font::Font;
+use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods};
use log::warn;
use lyon_geom::Arc;
+use range::Range;
use raqote::PathOp;
use style::color::AbsoluteColor;
-use crate::canvas_data;
use crate::canvas_data::{
- Backend, CanvasPaintState, Color, CompositionOp, DrawOptions, Filter, GenericDrawTarget,
- GenericPathBuilder, GradientStop, GradientStops, Path, SourceSurface, StrokeOptions,
+ self, Backend, CanvasPaintState, Color, CompositionOp, DrawOptions, Filter, GenericDrawTarget,
+ GenericPathBuilder, GradientStop, GradientStops, Path, SourceSurface, StrokeOptions, TextRun,
};
use crate::canvas_paint_thread::AntialiasMode;
+thread_local! {
+ /// The shared font cache used by all canvases that render on a thread. It would be nicer
+ /// to have a global cache, but it looks like font-kit uses a per-thread FreeType, so
+ /// in order to ensure that fonts are particular to a thread we have to make our own
+ /// cache thread local as well.
+ static SHARED_FONT_CACHE: RefCell<HashMap<FontIdentifier, Font>> = RefCell::default();
+}
+
+#[derive(Default)]
pub struct RaqoteBackend;
impl Backend for RaqoteBackend {
@@ -508,43 +521,61 @@ impl GenericDrawTarget for raqote::DrawTarget {
fn fill_text(
&mut self,
- font: &Font,
- point_size: f32,
- text: &str,
+ text_runs: Vec<TextRun>,
start: Point2D<f32>,
pattern: &canvas_data::Pattern,
- options: &DrawOptions,
+ draw_options: &DrawOptions,
) {
- let mut start = pathfinder_geometry::vector::vec2f(start.x, start.y);
- let mut ids = Vec::new();
- let mut positions = Vec::new();
- for c in text.chars() {
- let id = match font.glyph_for_char(c) {
- Some(id) => id,
- None => {
- warn!("Skipping non-existent glyph {}", c);
- continue;
- },
- };
- ids.push(id);
- positions.push(Point2D::new(start.x(), start.y()));
- let advance = match font.advance(id) {
- Ok(advance) => advance,
- Err(e) => {
- warn!("Skipping glyph {} with missing advance: {:?}", c, e);
- continue;
- },
- };
- start += advance * point_size / 24. / 96.;
- }
- self.draw_glyphs(
- font,
- point_size,
- &ids,
- &positions,
- &pattern.source(),
- options.as_raqote(),
- );
+ let mut advance = 0.;
+ for run in text_runs.iter() {
+ let mut positions = Vec::new();
+ let glyphs = &run.glyphs;
+ let ids: Vec<_> = glyphs
+ .iter_glyphs_for_byte_range(&Range::new(ByteIndex(0), glyphs.len()))
+ .map(|glyph| {
+ let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
+ positions.push(Point2D::new(
+ advance + start.x + glyph_offset.x.to_f32_px(),
+ start.y + glyph_offset.y.to_f32_px(),
+ ));
+ advance += glyph.advance().to_f32_px();
+ glyph.id()
+ })
+ .collect();
+
+ // TODO: raqote uses font-kit to rasterize glyphs, but font-kit fails an assertion when
+ // using color bitmap fonts in the FreeType backend. For now, simply do not render these
+ // type of fonts.
+ if run.font.has_color_bitmap_or_colr_table() {
+ continue;
+ }
+
+ let template = &run.font.template;
+
+ SHARED_FONT_CACHE.with(|font_cache| {
+ let identifier = template.identifier();
+ if !font_cache.borrow().contains_key(&identifier) {
+ let Ok(font) = Font::from_bytes(template.data(), identifier.index()) else {
+ return;
+ };
+ font_cache.borrow_mut().insert(identifier.clone(), font);
+ }
+
+ let font_cache = font_cache.borrow();
+ let Some(font) = font_cache.get(&identifier) else {
+ return;
+ };
+
+ self.draw_glyphs(
+ &font,
+ run.font.descriptor.pt_size.to_f32_px(),
+ &ids,
+ &positions,
+ &pattern.source(),
+ draw_options.as_raqote(),
+ );
+ })
+ }
}
fn fill_rect(
diff --git a/components/fonts/font.rs b/components/fonts/font.rs
index dd3cbe7d2cc..719ec70f144 100644
--- a/components/fonts/font.rs
+++ b/components/fonts/font.rs
@@ -480,9 +480,7 @@ pub struct FontGroup {
}
impl FontGroup {
- pub fn new(style: &FontStyleStruct) -> FontGroup {
- let descriptor = FontDescriptor::from(style);
-
+ pub fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
let families: SmallVec<[FontGroupFamily; 8]> = style
.font_family
.families
diff --git a/components/fonts/font_context.rs b/components/fonts/font_context.rs
index efadb035067..140fa56f14d 100644
--- a/components/fonts/font_context.rs
+++ b/components/fonts/font_context.rs
@@ -681,7 +681,11 @@ impl<FCT: FontSource> CachingFontSource<FCT> {
if let Some(font_group) = self.resolved_font_groups.read().get(&cache_key) {
return font_group.clone();
}
- let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style)));
+
+ let mut descriptor = FontDescriptor::from(&*cache_key.style);
+ descriptor.pt_size = size;
+
+ let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style, descriptor)));
self.resolved_font_groups
.write()
.insert(cache_key, font_group.clone());