1use kas::draw::{ImageFormat, ImageHandle};
9use kas::layout::{LogicalSize, PixmapScaling};
10use kas::prelude::*;
11use std::future::Future;
12use std::path::{Path, PathBuf};
13use std::sync::Arc;
14use tiny_skia::{Pixmap, Transform};
15use usvg::Tree;
16
17#[derive(thiserror::Error, Debug)]
19enum LoadError {
20 #[error("IO error")]
21 Io(#[from] std::io::Error),
22 #[error("SVG error")]
23 Svg(#[from] usvg::Error),
24}
25
26fn load(data: &[u8], resources_dir: Option<&Path>) -> Result<Tree, usvg::Error> {
27 use once_cell::sync::Lazy;
28 static FONT_FAMILY: Lazy<String> = Lazy::new(|| {
29 let mut resolver = kas::text::fonts::library().resolver();
30 resolver
31 .font_family_from_generic(kas::text::fonts::GenericFamily::Serif)
32 .map(|s| s.to_string())
33 .unwrap_or_default()
34 });
35
36 let opts = usvg::Options {
44 resources_dir: resources_dir.map(|path| path.to_owned()),
45 dpi: 96.0,
46 font_family: FONT_FAMILY.clone(),
47 font_size: 16.0, languages: vec!["en".to_string()],
49 shape_rendering: usvg::ShapeRendering::default(),
50 text_rendering: usvg::TextRendering::default(),
51 image_rendering: usvg::ImageRendering::default(),
52 default_size: usvg::Size::from_wh(100.0, 100.0).unwrap(),
53 image_href_resolver: Default::default(),
54 font_resolver: Default::default(),
55 fontdb: Default::default(),
56 style_sheet: None,
57 };
58
59 let tree = Tree::from_data(data, &opts)?;
60
61 Ok(tree)
62}
63
64#[derive(Clone)]
65enum Source {
66 Static(&'static [u8], Option<PathBuf>),
67 Heap(Arc<[u8]>, Option<PathBuf>),
68}
69impl std::fmt::Debug for Source {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 match self {
72 Source::Static(_, path) => write!(f, "Source::Static(_, {path:?}"),
73 Source::Heap(_, path) => write!(f, "Source::Heap(_, {path:?}"),
74 }
75 }
76}
77impl Source {
78 fn tree(&self) -> Result<Tree, usvg::Error> {
79 let (data, res_dir) = match self {
80 Source::Static(d, p) => (*d, p.as_ref()),
81 Source::Heap(d, p) => (&**d, p.as_ref()),
82 };
83 load(data, res_dir.map(|p| p.as_ref()))
84 }
85}
86
87#[derive(Clone, Debug, Default)]
88enum State {
89 #[default]
90 None,
91 Initial(Source),
92 Rendering(Source),
93 Ready(Source, Pixmap),
94}
95
96async fn draw(svg: Source, mut pixmap: Pixmap) -> Pixmap {
97 if let Ok(tree) = svg.tree() {
98 let w = f32::conv(pixmap.width()) / tree.size().width();
99 let h = f32::conv(pixmap.height()) / tree.size().height();
100 let transform = Transform::from_scale(w, h);
101 resvg::render(&tree, transform, &mut pixmap.as_mut());
102 }
103 pixmap
104}
105
106impl State {
107 fn resize(&mut self, (w, h): (u32, u32)) -> Option<impl Future<Output = Pixmap> + use<>> {
111 let old_state = std::mem::replace(self, State::None);
112 match old_state {
113 State::None => (),
114 state @ State::Rendering(_) => *self = state,
115 State::Ready(svg, px) if (px.width(), px.height()) == (w, h) => {
116 *self = State::Ready(svg, px);
117 return None;
118 }
119 State::Initial(svg) | State::Ready(svg, _) => {
120 if let Some(px) = Pixmap::new(w, h) {
121 *self = State::Rendering(svg.clone());
122 return Some(draw(svg, px));
123 } else {
124 *self = State::Initial(svg);
125 return None;
126 }
127 }
128 }
129 None
130 }
131}
132
133#[impl_self]
134mod Svg {
135 #[autoimpl(Debug ignore self.inner)]
139 #[derive(Clone, Default)]
140 #[widget]
141 pub struct Svg {
142 core: widget_core!(),
143 inner: State,
144 scaling: PixmapScaling,
145 image: Option<ImageHandle>,
146 }
147
148 impl Self {
149 pub fn new(data: &'static [u8]) -> Result<Self, impl std::error::Error> {
154 let mut svg = Svg::default();
155 let source = Source::Static(data, None);
156 svg.load_source(source).map(|_| svg)
157 }
158
159 pub fn new_path<P: AsRef<Path>>(path: P) -> Result<Self, impl std::error::Error> {
161 let mut svg = Svg::default();
162 svg._load_path(path.as_ref())?;
163 Result::<Self, LoadError>::Ok(svg)
164 }
165
166 pub fn load(
171 &mut self,
172 cx: &mut EventState,
173 data: &'static [u8],
174 resources_dir: Option<&Path>,
175 ) -> Result<(), impl std::error::Error + use<>> {
176 let source = Source::Static(data, resources_dir.map(|p| p.to_owned()));
177 self.load_source(source).map(|_| cx.resize(self))
178 }
179
180 fn load_source(&mut self, source: Source) -> Result<(), usvg::Error> {
181 let size = source.tree()?.size();
183 self.scaling.size = LogicalSize(size.width(), size.height());
184
185 self.inner = match std::mem::take(&mut self.inner) {
186 State::Ready(_, px) => State::Ready(source, px),
187 _ => State::Initial(source),
188 };
189 Ok(())
190 }
191
192 pub fn load_path<P: AsRef<Path>>(
196 &mut self,
197 cx: &mut EventState,
198 path: P,
199 ) -> Result<(), impl std::error::Error + use<P>> {
200 self._load_path(path.as_ref()).map(|_| cx.resize(self))
201 }
202
203 fn _load_path(&mut self, path: &Path) -> Result<(), LoadError> {
204 let buf = std::fs::read(path)?;
205 let rd = path.parent().map(|path| path.to_owned());
206 let source = Source::Heap(buf.into(), rd);
207 Ok(self.load_source(source)?)
208 }
209
210 #[inline]
215 #[must_use]
216 pub fn with_size(mut self, size: LogicalSize) -> Self {
217 self.scaling.size = size;
218 self
219 }
220
221 #[inline]
226 #[must_use]
227 pub fn with_scaling(mut self, f: impl FnOnce(&mut PixmapScaling)) -> Self {
228 f(&mut self.scaling);
229 self
230 }
231
232 #[inline]
237 pub fn set_scaling(&mut self, cx: &mut EventState, f: impl FnOnce(&mut PixmapScaling)) {
238 f(&mut self.scaling);
239 cx.resize(self);
241 }
242 }
243
244 impl Layout for Self {
245 fn rect(&self) -> Rect {
246 self.scaling.rect
247 }
248
249 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
250 self.scaling.size_rules(sizer, axis)
251 }
252
253 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
254 let align = hints.complete_default();
255 let scale_factor = cx.size_cx().scale_factor();
256 self.scaling.set_rect(rect, align, scale_factor);
257
258 let size: (u32, u32) = self.rect().size.cast();
259 if let Some(fut) = self.inner.resize(size) {
260 cx.send_spawn(self.id(), fut);
261 }
262 }
263
264 fn draw(&self, mut draw: DrawCx) {
265 if let Some(id) = self.image.as_ref().map(|h| h.id()) {
266 draw.image(self.rect(), id);
267 }
268 }
269 }
270
271 impl Tile for Self {
272 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
273 Role::Image
274 }
275 }
276
277 impl Events for Self {
278 type Data = ();
279
280 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
281 if let Some(pixmap) = cx.try_pop::<Pixmap>() {
282 let size = (pixmap.width(), pixmap.height());
283 let ds = cx.draw_shared();
284
285 if let Some(im_size) = self.image.as_ref().and_then(|h| ds.image_size(h))
286 && im_size != Size::conv(size)
287 && let Some(handle) = self.image.take()
288 {
289 ds.image_free(handle);
290 }
291
292 if self.image.is_none() {
293 self.image = ds.image_alloc(size).ok();
294 }
295
296 if let Some(handle) = self.image.as_ref() {
297 ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8);
298 }
299
300 cx.redraw(&self);
301 self.inner = match std::mem::take(&mut self.inner) {
302 State::None => State::None,
303 State::Initial(source) | State::Rendering(source) | State::Ready(source, _) => {
304 State::Ready(source, pixmap)
305 }
306 };
307
308 let own_size: (u32, u32) = self.rect().size.cast();
309 if size != own_size
310 && let Some(fut) = self.inner.resize(own_size)
311 {
312 cx.send_spawn(self.id(), fut);
313 }
314 }
315 }
316 }
317}