1use super::Scaling;
9use kas::draw::{ImageFormat, ImageHandle};
10use kas::layout::LogicalSize;
11use kas::prelude::*;
12use kas::theme::MarginStyle;
13use std::future::Future;
14use std::path::{Path, PathBuf};
15use std::sync::Arc;
16use tiny_skia::{Pixmap, Transform};
17use usvg::Tree;
18
19#[derive(thiserror::Error, Debug)]
21enum LoadError {
22 #[error("IO error")]
23 Io(#[from] std::io::Error),
24 #[error("SVG error")]
25 Svg(#[from] usvg::Error),
26}
27
28fn load(data: &[u8], resources_dir: Option<&Path>) -> Result<Tree, usvg::Error> {
29 use once_cell::sync::Lazy;
30 static FONT_FAMILY: Lazy<String> = Lazy::new(|| {
31 let mut resolver = kas::text::fonts::library().resolver();
32 resolver
33 .font_family_from_generic(kas::text::fonts::GenericFamily::Serif)
34 .map(|s| s.to_string())
35 .unwrap_or_default()
36 });
37
38 let opts = usvg::Options {
46 resources_dir: resources_dir.map(|path| path.to_owned()),
47 dpi: 96.0,
48 font_family: FONT_FAMILY.clone(),
49 font_size: 16.0, languages: vec!["en".to_string()],
51 shape_rendering: usvg::ShapeRendering::default(),
52 text_rendering: usvg::TextRendering::default(),
53 image_rendering: usvg::ImageRendering::default(),
54 default_size: usvg::Size::from_wh(100.0, 100.0).unwrap(),
55 image_href_resolver: Default::default(),
56 font_resolver: Default::default(),
57 fontdb: Default::default(),
58 style_sheet: None,
59 };
60
61 let tree = Tree::from_data(data, &opts)?;
62
63 Ok(tree)
64}
65
66#[derive(Clone)]
67enum Source {
68 Static(&'static [u8], Option<PathBuf>),
69 Heap(Arc<[u8]>, Option<PathBuf>),
70}
71impl std::fmt::Debug for Source {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 match self {
74 Source::Static(_, path) => write!(f, "Source::Static(_, {path:?}"),
75 Source::Heap(_, path) => write!(f, "Source::Heap(_, {path:?}"),
76 }
77 }
78}
79impl Source {
80 fn tree(&self) -> Result<Tree, usvg::Error> {
81 let (data, res_dir) = match self {
82 Source::Static(d, p) => (*d, p.as_ref()),
83 Source::Heap(d, p) => (&**d, p.as_ref()),
84 };
85 load(data, res_dir.map(|p| p.as_ref()))
86 }
87}
88
89#[derive(Clone, Debug, Default)]
90enum State {
91 #[default]
92 None,
93 Initial(Source),
94 Rendering(Source),
95 Ready(Source, Pixmap),
96}
97
98async fn draw(svg: Source, mut pixmap: Pixmap) -> Pixmap {
99 if let Ok(tree) = svg.tree() {
100 let w = f32::conv(pixmap.width()) / tree.size().width();
101 let h = f32::conv(pixmap.height()) / tree.size().height();
102 let transform = Transform::from_scale(w, h);
103 resvg::render(&tree, transform, &mut pixmap.as_mut());
104 }
105 pixmap
106}
107
108impl State {
109 fn resize(&mut self, (w, h): (u32, u32)) -> Option<impl Future<Output = Pixmap> + use<>> {
113 let old_state = std::mem::replace(self, State::None);
114 match old_state {
115 State::None => (),
116 state @ State::Rendering(_) => *self = state,
117 State::Ready(svg, px) if (px.width(), px.height()) == (w, h) => {
118 *self = State::Ready(svg, px);
119 return None;
120 }
121 State::Initial(svg) | State::Ready(svg, _) => {
122 if let Some(px) = Pixmap::new(w, h) {
123 *self = State::Rendering(svg.clone());
124 return Some(draw(svg, px));
125 } else {
126 *self = State::Initial(svg);
127 return None;
128 }
129 }
130 }
131 None
132 }
133}
134
135#[impl_self]
136mod Svg {
137 #[autoimpl(Debug ignore self.inner)]
150 #[derive(Default)]
151 #[widget]
152 pub struct Svg {
153 core: widget_core!(),
154 inner: State,
155 scaling: Scaling,
156 image: Option<ImageHandle>,
157 }
158
159 impl Self {
160 pub fn new(data: &'static [u8]) -> Result<Self, impl std::error::Error> {
167 let mut svg = Svg::default();
168 let source = Source::Static(data, None);
169 svg.load_source(source).map(|_| svg)
170 }
171
172 pub fn new_path<P: AsRef<Path>>(path: P) -> Result<Self, impl std::error::Error> {
174 let mut svg = Svg::default();
175 svg._load_path(path.as_ref())?;
176 Result::<Self, LoadError>::Ok(svg)
177 }
178
179 pub fn load(
184 &mut self,
185 cx: &mut ConfigCx,
186 data: &'static [u8],
187 resources_dir: Option<&Path>,
188 ) -> Result<(), impl std::error::Error + use<>> {
189 let source = Source::Static(data, resources_dir.map(|p| p.to_owned()));
190 self.load_source(source).map(|_| cx.resize())
191 }
192
193 fn load_source(&mut self, source: Source) -> Result<(), usvg::Error> {
194 let size = source.tree()?.size();
196 self.scaling.size = LogicalSize(size.width(), size.height());
197
198 self.inner = match std::mem::take(&mut self.inner) {
199 State::Ready(_, px) => State::Ready(source, px),
200 _ => State::Initial(source),
201 };
202 Ok(())
203 }
204
205 pub fn load_path<P: AsRef<Path>>(
209 &mut self,
210 cx: &mut ConfigCx,
211 path: P,
212 ) -> Result<(), impl std::error::Error + use<P>> {
213 self._load_path(path.as_ref()).map(|_| cx.resize())
214 }
215
216 fn _load_path(&mut self, path: &Path) -> Result<(), LoadError> {
217 let buf = std::fs::read(path)?;
218 let rd = path.parent().map(|path| path.to_owned());
219 let source = Source::Heap(buf.into(), rd);
220 Ok(self.load_source(source)?)
221 }
222
223 pub fn set_logical_size(&mut self, size: impl Into<LogicalSize>) {
225 self.scaling.size = size.into();
226 }
227
228 #[must_use]
230 pub fn with_logical_size(mut self, size: impl Into<LogicalSize>) -> Self {
231 self.scaling.size = size.into();
232 self
233 }
234
235 #[must_use]
239 #[inline]
240 pub fn with_margin_style(mut self, style: MarginStyle) -> Self {
241 self.scaling.margins = style;
242 self
243 }
244
245 #[must_use]
249 #[inline]
250 pub fn with_fixed_aspect_ratio(mut self, fixed: bool) -> Self {
251 self.scaling.fix_aspect = fixed;
252 self
253 }
254
255 #[must_use]
260 #[inline]
261 pub fn with_stretch(mut self, stretch: Stretch) -> Self {
262 self.scaling.stretch = stretch;
263 self
264 }
265 }
266
267 impl Layout for Self {
268 fn size_rules(&mut self, cx: &mut SizeCx, axis: AxisInfo) -> SizeRules {
269 self.scaling.size_rules(cx, axis)
270 }
271
272 fn set_rect(&mut self, cx: &mut SizeCx, rect: Rect, hints: AlignHints) {
273 let align = hints.complete_default();
274 let scale_factor = cx.scale_factor();
275 let rect = self.scaling.align(rect, align, scale_factor);
276 self.core.set_rect(rect);
277
278 let size: (u32, u32) = self.rect().size.cast();
279 if let Some(fut) = self.inner.resize(size) {
280 cx.send_spawn(self.id(), fut);
281 }
282 }
283
284 fn draw(&self, mut draw: DrawCx) {
285 if let Some(id) = self.image.as_ref().map(|h| h.id()) {
286 draw.image(self.rect(), id);
287 }
288 }
289 }
290
291 impl Tile for Self {
292 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
293 Role::Image
294 }
295 }
296
297 impl Events for Self {
298 type Data = ();
299
300 fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
301 if let Some(pixmap) = cx.try_pop::<Pixmap>() {
302 let size = (pixmap.width(), pixmap.height()).cast();
303 let ds = cx.draw_shared();
304
305 if let Some(im_size) = self.image.as_ref().and_then(|h| ds.image_size(h))
306 && im_size != size
307 && let Some(handle) = self.image.take()
308 {
309 ds.image_free(handle);
310 }
311
312 if self.image.is_none() {
313 self.image = ds.image_alloc(ImageFormat::Rgba8, size).ok();
314 }
315
316 if let Some(handle) = self.image.as_ref() {
317 match ds.image_upload(handle, pixmap.data()) {
318 Ok(_) => cx.redraw(),
319 Err(err) => log::warn!("Svg: image upload failed: {err}"),
320 }
321 }
322
323 self.inner = match std::mem::take(&mut self.inner) {
324 State::None => State::None,
325 State::Initial(source) | State::Rendering(source) | State::Ready(source, _) => {
326 State::Ready(source, pixmap)
327 }
328 };
329
330 if size != self.rect().size
331 && let Some(fut) = self.inner.resize(self.rect().size.cast())
332 {
333 cx.send_spawn(self.id(), fut);
334 }
335 }
336 }
337 }
338}