Closes https://github.com/zed-industries/zed/issues/39104 This fixes an issue where the preview would not work for remote buffers in the process. Release Notes: - Fixed an issue where the SVG preview would not work in remote scenarios. - The SVG preview will now rerender on every keypress instead of only on saves.
143 lines
4.5 KiB
Rust
143 lines
4.5 KiB
Rust
use crate::{
|
|
AssetSource, DevicePixels, IsZero, RenderImage, Result, SharedString, Size,
|
|
swap_rgba_pa_to_bgra,
|
|
};
|
|
use image::Frame;
|
|
use resvg::tiny_skia::Pixmap;
|
|
use smallvec::SmallVec;
|
|
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.;
|
|
|
|
#[derive(Clone, PartialEq, Hash, Eq)]
|
|
pub(crate) struct RenderSvgParams {
|
|
pub(crate) path: SharedString,
|
|
pub(crate) size: Size<DevicePixels>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
/// A struct holding everything necessary to render SVGs.
|
|
pub struct SvgRenderer {
|
|
asset_source: Arc<dyn AssetSource>,
|
|
usvg_options: Arc<usvg::Options<'static>>,
|
|
}
|
|
|
|
/// The size in which to render the SVG.
|
|
pub enum SvgSize {
|
|
/// An absolute size in device pixels.
|
|
Size(Size<DevicePixels>),
|
|
/// A scaling factor to apply to the size provided by the SVG.
|
|
ScaleFactor(f32),
|
|
}
|
|
|
|
impl SvgRenderer {
|
|
/// Creates a new SVG renderer with the provided asset source.
|
|
pub fn new(asset_source: Arc<dyn AssetSource>) -> Self {
|
|
static FONT_DB: LazyLock<Arc<usvg::fontdb::Database>> = 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),
|
|
}
|
|
}
|
|
|
|
/// Renders the given bytes into an image buffer.
|
|
pub fn render_single_frame(
|
|
&self,
|
|
bytes: &[u8],
|
|
scale_factor: f32,
|
|
to_brga: bool,
|
|
) -> Result<Arc<RenderImage>, usvg::Error> {
|
|
self.render_pixmap(
|
|
bytes,
|
|
SvgSize::ScaleFactor(scale_factor * SMOOTH_SVG_SCALE_FACTOR),
|
|
)
|
|
.map(|pixmap| {
|
|
let mut buffer =
|
|
image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
|
|
.unwrap();
|
|
|
|
if to_brga {
|
|
for pixel in buffer.chunks_exact_mut(4) {
|
|
swap_rgba_pa_to_bgra(pixel);
|
|
}
|
|
}
|
|
|
|
let mut image = RenderImage::new(SmallVec::from_const([Frame::new(buffer)]));
|
|
image.scale_factor = SMOOTH_SVG_SCALE_FACTOR;
|
|
Arc::new(image)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn render_alpha_mask(
|
|
&self,
|
|
params: &RenderSvgParams,
|
|
) -> Result<Option<(Size<DevicePixels>, Vec<u8>)>> {
|
|
anyhow::ensure!(!params.size.is_zero(), "can't render at a zero size");
|
|
|
|
// Load the tree.
|
|
let Some(bytes) = self.asset_source.load(¶ms.path)? else {
|
|
return Ok(None);
|
|
};
|
|
|
|
let pixmap = self.render_pixmap(&bytes, SvgSize::Size(params.size))?;
|
|
|
|
// Convert the pixmap's pixels into an alpha mask.
|
|
let size = Size::new(
|
|
DevicePixels(pixmap.width() as i32),
|
|
DevicePixels(pixmap.height() as i32),
|
|
);
|
|
let alpha_mask = pixmap
|
|
.pixels()
|
|
.iter()
|
|
.map(|p| p.alpha())
|
|
.collect::<Vec<_>>();
|
|
Ok(Some((size, alpha_mask)))
|
|
}
|
|
|
|
fn render_pixmap(&self, bytes: &[u8], size: SvgSize) -> Result<Pixmap, usvg::Error> {
|
|
let tree = usvg::Tree::from_data(bytes, &self.usvg_options)?;
|
|
let svg_size = tree.size();
|
|
let scale = match size {
|
|
SvgSize::Size(size) => size.width.0 as f32 / svg_size.width(),
|
|
SvgSize::ScaleFactor(scale) => scale,
|
|
};
|
|
|
|
// Render the SVG to a pixmap with the specified width and height.
|
|
let mut pixmap = resvg::tiny_skia::Pixmap::new(
|
|
(svg_size.width() * scale) as u32,
|
|
(svg_size.height() * scale) as u32,
|
|
)
|
|
.ok_or(usvg::Error::InvalidSize)?;
|
|
|
|
let transform = resvg::tiny_skia::Transform::from_scale(scale, scale);
|
|
|
|
resvg::render(&tree, transform, &mut pixmap.as_mut());
|
|
|
|
Ok(pixmap)
|
|
}
|
|
}
|