gpui: Add support for text in SVGs (#26335)

Closes #21319
Before: 

![image](https://github.com/user-attachments/assets/f75d7d59-75b1-4836-ae3b-6a1f526a5833)
After:

![image](https://github.com/user-attachments/assets/5fa28a6d-c417-4777-99f8-2a17edf759a0)

Use fontdb to load system fonts and pass it to resvg renderer. This adds
a small increase in startup time (around 30ms on my Linux system to
traverse fonts on a cold start). In the future once cosmic-text bumps
their version of fontdb we could clone the Database from
CosmicTextSystem

Release Notes: 
- Added: support for rendering text in SVGs
This commit is contained in:
Kamal Ahmad 2025-03-14 18:25:11 +05:00 committed by GitHub
parent 6a95ec6a64
commit 41c373eff1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 126 additions and 25 deletions

View file

@ -1,7 +1,10 @@
use crate::{AssetSource, DevicePixels, IsZero, Result, SharedString, Size};
use anyhow::anyhow;
use resvg::tiny_skia::Pixmap;
use std::{hash::Hash, sync::Arc};
use std::{
hash::Hash,
sync::{Arc, LazyLock},
};
/// When rendering SVGs, we render them at twice the size to get a higher-quality result.
pub const SMOOTH_SVG_SCALE_FACTOR: f32 = 2.;
@ -15,6 +18,7 @@ pub(crate) struct RenderSvgParams {
#[derive(Clone)]
pub struct SvgRenderer {
asset_source: Arc<dyn AssetSource>,
usvg_options: Arc<usvg::Options<'static>>,
}
pub enum SvgSize {
@ -24,7 +28,31 @@ pub enum SvgSize {
impl SvgRenderer {
pub fn new(asset_source: Arc<dyn AssetSource>) -> Self {
Self { asset_source }
let font_db = LazyLock::new(|| {
let mut db = usvg::fontdb::Database::new();
db.load_system_fonts();
Arc::new(db)
});
let default_font_resolver = usvg::FontResolver::default_font_selector();
let font_resolver = Box::new(
move |font: &usvg::Font, db: &mut Arc<usvg::fontdb::Database>| {
if db.is_empty() {
*db = font_db.clone();
}
default_font_resolver(font, db)
},
);
let options = usvg::Options {
font_resolver: usvg::FontResolver {
select_font: font_resolver,
select_fallback: usvg::FontResolver::default_fallback_selector(),
},
..Default::default()
};
Self {
asset_source,
usvg_options: Arc::new(options),
}
}
pub(crate) fn render(&self, params: &RenderSvgParams) -> Result<Option<Vec<u8>>> {
@ -49,7 +77,7 @@ impl SvgRenderer {
}
pub fn render_pixmap(&self, bytes: &[u8], size: SvgSize) -> Result<Pixmap, usvg::Error> {
let tree = usvg::Tree::from_data(bytes, &usvg::Options::default())?;
let tree = usvg::Tree::from_data(bytes, &self.usvg_options)?;
let size = match size {
SvgSize::Size(size) => size,