Implement SVG rendering
This commit is contained in:
+15
-1
@@ -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
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user