Implement SVG rendering

This commit is contained in:
Antonio Scandurra
2021-04-06 13:46:33 +02:00
parent 2970e934da
commit e0c43dac25
9 changed files with 505 additions and 49 deletions
+15 -1
View File
@@ -1,5 +1,5 @@
use anyhow::{anyhow, Result};
use std::borrow::Cow;
use std::{borrow::Cow, cell::RefCell, collections::HashMap};
pub trait AssetSource: 'static {
fn load(&self, path: &str) -> Result<Cow<[u8]>>;
@@ -16,12 +16,26 @@ impl AssetSource for () {
pub struct AssetCache {
source: Box<dyn AssetSource>,
svgs: RefCell<HashMap<String, usvg::Tree>>,
}
impl AssetCache {
pub fn new(source: impl AssetSource) -> Self {
Self {
source: Box::new(source),
svgs: RefCell::new(HashMap::new()),
}
}
pub fn svg(&self, path: &str) -> Result<usvg::Tree> {
let mut svgs = self.svgs.borrow_mut();
if let Some(svg) = svgs.get(path) {
Ok(svg.clone())
} else {
let bytes = self.source.load(path)?;
let svg = usvg::Tree::from_data(&bytes, &usvg::Options::default())?;
svgs.insert(path.to_string(), svg.clone());
Ok(svg)
}
}
}
+60 -41
View File
@@ -1,73 +1,85 @@
use crate::{
geometry::vector::Vector2F, AfterLayoutContext, Element, Event, EventContext, LayoutContext,
PaintContext, SizeConstraint,
color::ColorU,
geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
},
scene, AfterLayoutContext, Element, Event, EventContext, LayoutContext, PaintContext,
SizeConstraint,
};
pub struct Svg {
path: String,
color: ColorU,
}
impl Svg {
pub fn new(path: String) -> Self {
Self { path }
Self {
path,
color: ColorU::black(),
}
}
pub fn with_color(mut self, color: ColorU) -> Self {
self.color = color;
self
}
}
impl Element for Svg {
type LayoutState = ();
type LayoutState = Option<usvg::Tree>;
type PaintState = ();
fn layout(
&mut self,
_: SizeConstraint,
_: &mut LayoutContext,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
) -> (Vector2F, Self::LayoutState) {
// let size;
// match ctx.asset_cache.svg(&self.path) {
// Ok(tree) => {
// size = if constraint.max.x().is_infinite() && constraint.max.y().is_infinite() {
// let rect = usvg_rect_to_euclid_rect(&tree.svg_node().view_box.rect);
// rect.size()
// } else {
// let max_size = constraint.max;
// let svg_size = usvg_rect_to_euclid_rect(&tree.svg_node().view_box.rect).size();
match ctx.asset_cache.svg(&self.path) {
Ok(tree) => {
let size = if constraint.max.x().is_infinite() && constraint.max.y().is_infinite() {
let rect = from_usvg_rect(tree.svg_node().view_box.rect);
rect.size()
} else {
let max_size = constraint.max;
let svg_size = from_usvg_rect(tree.svg_node().view_box.rect).size();
// if max_size.x().is_infinite()
// || max_size.x() / max_size.y() > svg_size.x() / svg_size.y()
// {
// vec2f(svg_size.x() * max_size.y() / svg_size.y(), max_size.y())
// } else {
// vec2f(max_size.x(), svg_size.y() * max_size.x() / svg_size.x())
// }
// };
// self.tree = Some(tree);
// }
// Err(error) => {
// log::error!("{}", error);
// size = constraint.min;
// }
// };
// size
todo!()
if max_size.x().is_infinite()
|| max_size.x() / max_size.y() > svg_size.x() / svg_size.y()
{
vec2f(svg_size.x() * max_size.y() / svg_size.y(), max_size.y())
} else {
vec2f(max_size.x(), svg_size.y() * max_size.x() / svg_size.x())
}
};
(size, Some(tree))
}
Err(error) => {
log::error!("{}", error);
(constraint.min, None)
}
}
}
fn after_layout(&mut self, _: Vector2F, _: &mut Self::LayoutState, _: &mut AfterLayoutContext) {
}
fn paint(
&mut self,
_: pathfinder_geometry::rect::RectF,
_: &mut Self::LayoutState,
_: &mut PaintContext,
) -> Self::PaintState {
fn paint(&mut self, bounds: RectF, svg: &mut Self::LayoutState, ctx: &mut PaintContext) {
if let Some(svg) = svg.clone() {
ctx.scene.push_icon(scene::Icon {
bounds,
svg,
path: self.path.clone(),
color: self.color,
});
}
}
fn dispatch_event(
&mut self,
_: &Event,
_: pathfinder_geometry::rect::RectF,
_: RectF,
_: &mut Self::LayoutState,
_: &mut Self::PaintState,
_: &mut EventContext,
@@ -75,3 +87,10 @@ impl Element for Svg {
false
}
}
fn from_usvg_rect(rect: usvg::Rect) -> RectF {
RectF::new(
vec2f(rect.x() as f32, rect.y() as f32),
vec2f(rect.width() as f32, rect.height() as f32),
)
}
+26 -3
View File
@@ -296,7 +296,7 @@ impl Renderer {
drawable_size,
command_encoder,
);
self.render_glyph_sprites(scene, layer, offset, drawable_size, command_encoder);
self.render_sprites(scene, layer, offset, drawable_size, command_encoder);
}
command_encoder.end_encoding();
@@ -465,7 +465,7 @@ impl Renderer {
*offset = next_offset;
}
fn render_glyph_sprites(
fn render_sprites(
&mut self,
scene: &Scene,
layer: &Layer,
@@ -473,11 +473,12 @@ impl Renderer {
drawable_size: Vector2F,
command_encoder: &metal::RenderCommandEncoderRef,
) {
if layer.glyphs().is_empty() {
if layer.glyphs().is_empty() && layer.icons().is_empty() {
return;
}
let mut sprites_by_atlas = HashMap::new();
for glyph in layer.glyphs() {
if let Some(sprite) = self.sprite_cache.render_glyph(
glyph.font_id,
@@ -501,6 +502,28 @@ impl Renderer {
}
}
for icon in layer.icons() {
let sprite = self.sprite_cache.render_icon(
icon.bounds.size(),
icon.path.clone(),
icon.svg.clone(),
scene.scale_factor(),
);
// Snap sprite to pixel grid.
let origin = (icon.bounds.origin() * scene.scale_factor()).floor();
sprites_by_atlas
.entry(sprite.atlas_id)
.or_insert_with(Vec::new)
.push(shaders::GPUISprite {
origin: origin.to_float2(),
size: sprite.size.to_float2(),
atlas_origin: sprite.atlas_origin.to_float2(),
color: icon.color.to_uchar4(),
compute_winding: 0,
});
}
command_encoder.set_render_pipeline_state(&self.sprite_pipeline_state);
command_encoder.set_vertex_buffer(
shaders::GPUISpriteVertexInputIndex_GPUISpriteVertexInputIndexVertices as u64,
+64
View File
@@ -27,12 +27,27 @@ pub struct GlyphSprite {
pub size: Vector2I,
}
#[derive(Hash, Eq, PartialEq)]
struct IconDescriptor {
path: String,
width: i32,
height: i32,
}
#[derive(Clone)]
pub struct IconSprite {
pub atlas_id: usize,
pub atlas_origin: Vector2I,
pub size: Vector2I,
}
pub struct SpriteCache {
device: metal::Device,
atlas_size: Vector2I,
fonts: Arc<dyn platform::FontSystem>,
atlases: Vec<Atlas>,
glyphs: HashMap<GlyphDescriptor, Option<GlyphSprite>>,
icons: HashMap<IconDescriptor, IconSprite>,
}
impl SpriteCache {
@@ -48,6 +63,7 @@ impl SpriteCache {
fonts,
atlases,
glyphs: Default::default(),
icons: Default::default(),
}
}
@@ -119,6 +135,54 @@ impl SpriteCache {
.clone()
}
pub fn render_icon(
&mut self,
size: Vector2F,
path: String,
svg: usvg::Tree,
scale_factor: f32,
) -> IconSprite {
let atlases = &mut self.atlases;
let atlas_size = self.atlas_size;
let device = &self.device;
let size = (size * scale_factor).round().to_i32();
assert!(size.x() < atlas_size.x());
assert!(size.y() < atlas_size.y());
self.icons
.entry(IconDescriptor {
path,
width: size.x(),
height: size.y(),
})
.or_insert_with(|| {
let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32).unwrap();
resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
let mask = pixmap
.pixels()
.iter()
.map(|a| a.alpha())
.collect::<Vec<_>>();
let atlas_bounds = atlases
.last_mut()
.unwrap()
.try_insert(size, &mask)
.unwrap_or_else(|| {
let mut atlas = Atlas::new(device, atlas_size);
let bounds = atlas.try_insert(size, &mask).unwrap();
atlases.push(atlas);
bounds
});
IconSprite {
atlas_id: atlases.len() - 1,
atlas_origin: atlas_bounds.origin(),
size,
}
})
.clone()
}
pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
self.atlases.get(atlas_id).map(|a| a.texture.as_ref())
}
+22 -1
View File
@@ -10,12 +10,13 @@ pub struct Scene {
active_layer_stack: Vec<usize>,
}
#[derive(Default, Debug)]
#[derive(Default)]
pub struct Layer {
clip_bounds: Option<RectF>,
quads: Vec<Quad>,
shadows: Vec<Shadow>,
glyphs: Vec<Glyph>,
icons: Vec<Icon>,
paths: Vec<Path>,
}
@@ -44,6 +45,13 @@ pub struct Glyph {
pub color: ColorU,
}
pub struct Icon {
pub bounds: RectF,
pub svg: usvg::Tree,
pub path: String,
pub color: ColorU,
}
#[derive(Clone, Copy, Default, Debug)]
pub struct Border {
pub width: f32,
@@ -107,6 +115,10 @@ impl Scene {
self.active_layer().push_glyph(glyph)
}
pub fn push_icon(&mut self, icon: Icon) {
self.active_layer().push_icon(icon)
}
pub fn push_path(&mut self, path: Path) {
self.active_layer().push_path(path);
}
@@ -123,6 +135,7 @@ impl Layer {
quads: Vec::new(),
shadows: Vec::new(),
glyphs: Vec::new(),
icons: Vec::new(),
paths: Vec::new(),
}
}
@@ -155,6 +168,14 @@ impl Layer {
self.glyphs.as_slice()
}
pub fn push_icon(&mut self, icon: Icon) {
self.icons.push(icon);
}
pub fn icons(&self) -> &[Icon] {
self.icons.as_slice()
}
fn push_path(&mut self, path: Path) {
if !path.bounds.is_empty() {
self.paths.push(path);