mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 13:50:04 -04:00
Support SVG Text Rendering in egui_extras (#5979)
**Added** * Create `svg_text` feature flag to support text rendering & loading of system fonts. **Changed** * Updates `resvg` to `0.45`. * Adds `usvg::Options` field to the `SvgLoader` structure. * Change function signatures to support passing `usvg::Options` to downstream `load_svg_bytes_with_size`. **Additional Info** * I used this PR as a reference: https://github.com/emilk/egui/pull/4659. @xNWP can you see if this adequately resolves your concern from your original PR? * Closes https://github.com/emilk/egui/issues/5977 (we may want to open another issue for my other thoughts in this issue) * Also, I would like to thank @xNWP and their original PR for being a good reference for this one. * [x] I have followed the instructions in the PR template
This commit is contained in:
committed by
GitHub
parent
a8e0c56a8f
commit
0f1d6c2818
@@ -63,8 +63,12 @@ impl RetainedImage {
|
||||
/// # Errors
|
||||
/// On invalid image
|
||||
#[cfg(feature = "svg")]
|
||||
pub fn from_svg_bytes(debug_name: impl Into<String>, svg_bytes: &[u8]) -> Result<Self, String> {
|
||||
Self::from_svg_bytes_with_size(debug_name, svg_bytes, None)
|
||||
pub fn from_svg_bytes(
|
||||
debug_name: impl Into<String>,
|
||||
svg_bytes: &[u8],
|
||||
options: &resvg::usvg::Options<'_>,
|
||||
) -> Result<Self, String> {
|
||||
Self::from_svg_bytes_with_size(debug_name, svg_bytes, None, options)
|
||||
}
|
||||
|
||||
/// Pass in the str of an SVG that you've loaded.
|
||||
@@ -72,8 +76,12 @@ impl RetainedImage {
|
||||
/// # Errors
|
||||
/// On invalid image
|
||||
#[cfg(feature = "svg")]
|
||||
pub fn from_svg_str(debug_name: impl Into<String>, svg_str: &str) -> Result<Self, String> {
|
||||
Self::from_svg_bytes(debug_name, svg_str.as_bytes())
|
||||
pub fn from_svg_str(
|
||||
debug_name: impl Into<String>,
|
||||
svg_str: &str,
|
||||
options: &resvg::usvg::Options<'_>,
|
||||
) -> Result<Self, String> {
|
||||
Self::from_svg_bytes(debug_name, svg_str.as_bytes(), options)
|
||||
}
|
||||
|
||||
/// Pass in the bytes of an SVG that you've loaded
|
||||
@@ -86,10 +94,11 @@ impl RetainedImage {
|
||||
debug_name: impl Into<String>,
|
||||
svg_bytes: &[u8],
|
||||
size_hint: Option<SizeHint>,
|
||||
options: &resvg::usvg::Options<'_>,
|
||||
) -> Result<Self, String> {
|
||||
Ok(Self::from_color_image(
|
||||
debug_name,
|
||||
load_svg_bytes_with_size(svg_bytes, size_hint)?,
|
||||
load_svg_bytes_with_size(svg_bytes, size_hint, options)?,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -227,8 +236,11 @@ pub fn load_image_bytes(image_bytes: &[u8]) -> Result<egui::ColorImage, egui::lo
|
||||
/// # Errors
|
||||
/// On invalid image
|
||||
#[cfg(feature = "svg")]
|
||||
pub fn load_svg_bytes(svg_bytes: &[u8]) -> Result<egui::ColorImage, String> {
|
||||
load_svg_bytes_with_size(svg_bytes, None)
|
||||
pub fn load_svg_bytes(
|
||||
svg_bytes: &[u8],
|
||||
options: &resvg::usvg::Options<'_>,
|
||||
) -> Result<egui::ColorImage, String> {
|
||||
load_svg_bytes_with_size(svg_bytes, None, options)
|
||||
}
|
||||
|
||||
/// Load an SVG and rasterize it into an egui image with a scaling parameter.
|
||||
@@ -241,48 +253,47 @@ pub fn load_svg_bytes(svg_bytes: &[u8]) -> Result<egui::ColorImage, String> {
|
||||
pub fn load_svg_bytes_with_size(
|
||||
svg_bytes: &[u8],
|
||||
size_hint: Option<SizeHint>,
|
||||
options: &resvg::usvg::Options<'_>,
|
||||
) -> Result<egui::ColorImage, String> {
|
||||
use resvg::tiny_skia::{IntSize, Pixmap};
|
||||
use resvg::usvg::{Options, Tree, TreeParsing};
|
||||
use resvg::usvg::{Transform, Tree};
|
||||
|
||||
profiling::function_scope!();
|
||||
|
||||
let opt = Options::default();
|
||||
let rtree = Tree::from_data(svg_bytes, options).map_err(|err| err.to_string())?;
|
||||
|
||||
let mut rtree = Tree::from_data(svg_bytes, &opt).map_err(|err| err.to_string())?;
|
||||
|
||||
let mut size = rtree.size.to_int_size();
|
||||
match size_hint {
|
||||
None => (),
|
||||
Some(SizeHint::Size(w, h)) => {
|
||||
size = size.scale_to(
|
||||
IntSize::from_wh(w, h).ok_or_else(|| format!("Failed to scale SVG to {w}x{h}"))?,
|
||||
);
|
||||
}
|
||||
Some(SizeHint::Height(h)) => {
|
||||
size = size
|
||||
.scale_to_height(h)
|
||||
.ok_or_else(|| format!("Failed to scale SVG to height {h}"))?;
|
||||
}
|
||||
Some(SizeHint::Width(w)) => {
|
||||
size = size
|
||||
.scale_to_width(w)
|
||||
.ok_or_else(|| format!("Failed to scale SVG to width {w}"))?;
|
||||
}
|
||||
let size = rtree.size().to_int_size();
|
||||
let scaled_size = match size_hint {
|
||||
None => size,
|
||||
Some(SizeHint::Size(w, h)) => size.scale_to(
|
||||
IntSize::from_wh(w, h).ok_or_else(|| format!("Failed to scale SVG to {w}x{h}"))?,
|
||||
),
|
||||
Some(SizeHint::Height(h)) => size
|
||||
.scale_to_height(h)
|
||||
.ok_or_else(|| format!("Failed to scale SVG to height {h}"))?,
|
||||
Some(SizeHint::Width(w)) => size
|
||||
.scale_to_width(w)
|
||||
.ok_or_else(|| format!("Failed to scale SVG to width {w}"))?,
|
||||
Some(SizeHint::Scale(z)) => {
|
||||
let z_inner = z.into_inner();
|
||||
size = size
|
||||
.scale_by(z_inner)
|
||||
.ok_or_else(|| format!("Failed to scale SVG by {z_inner}"))?;
|
||||
size.scale_by(z_inner)
|
||||
.ok_or_else(|| format!("Failed to scale SVG by {z_inner}"))?
|
||||
}
|
||||
};
|
||||
let (w, h) = (size.width(), size.height());
|
||||
|
||||
let (w, h) = (scaled_size.width(), scaled_size.height());
|
||||
|
||||
let mut pixmap =
|
||||
Pixmap::new(w, h).ok_or_else(|| format!("Failed to create SVG Pixmap of size {w}x{h}"))?;
|
||||
|
||||
rtree.size = size.to_size();
|
||||
resvg::Tree::from_usvg(&rtree).render(Default::default(), &mut pixmap.as_mut());
|
||||
resvg::render(
|
||||
&rtree,
|
||||
Transform::from_scale(
|
||||
w as f32 / size.width() as f32,
|
||||
h as f32 / size.height() as f32,
|
||||
),
|
||||
&mut pixmap.as_mut(),
|
||||
);
|
||||
|
||||
let image = egui::ColorImage::from_rgba_unmultiplied([w as _, h as _], pixmap.data());
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ use egui::{
|
||||
|
||||
type Entry = Result<Arc<ColorImage>, String>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SvgLoader {
|
||||
cache: Mutex<HashMap<(Cow<'static, str>, SizeHint), Entry>>,
|
||||
options: resvg::usvg::Options<'static>,
|
||||
}
|
||||
|
||||
impl SvgLoader {
|
||||
@@ -27,6 +27,22 @@ fn is_supported(uri: &str) -> bool {
|
||||
ext == "svg"
|
||||
}
|
||||
|
||||
impl Default for SvgLoader {
|
||||
fn default() -> Self {
|
||||
// opt is mutated when `svg_text` feature flag is enabled
|
||||
#[allow(unused_mut)]
|
||||
let mut options = resvg::usvg::Options::default();
|
||||
|
||||
#[cfg(feature = "svg_text")]
|
||||
options.fontdb_mut().load_system_fonts();
|
||||
|
||||
Self {
|
||||
cache: Mutex::new(HashMap::default()),
|
||||
options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImageLoader for SvgLoader {
|
||||
fn id(&self) -> &str {
|
||||
Self::ID
|
||||
@@ -48,8 +64,12 @@ impl ImageLoader for SvgLoader {
|
||||
match ctx.try_load_bytes(uri) {
|
||||
Ok(BytesPoll::Ready { bytes, .. }) => {
|
||||
log::trace!("started loading {uri:?}");
|
||||
let result = crate::image::load_svg_bytes_with_size(&bytes, Some(size_hint))
|
||||
.map(Arc::new);
|
||||
let result = crate::image::load_svg_bytes_with_size(
|
||||
&bytes,
|
||||
Some(size_hint),
|
||||
&self.options,
|
||||
)
|
||||
.map(Arc::new);
|
||||
log::trace!("finished loading {uri:?}");
|
||||
cache.insert((Cow::Owned(uri.to_owned()), size_hint), result.clone());
|
||||
match result {
|
||||
|
||||
Reference in New Issue
Block a user