1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use crate::{
layout::RenderContext,
primitives::CrossOrigin,
utils::ElementWriter,
};
use enum_display::EnumDisplay;
use quick_xml::escape::escape;
use std::fmt::{
Display,
Formatter,
Write,
};
use taffy::Size;
#[derive(Debug, Clone, EnumDisplay)]
enum ImageSourceInner {
/// External image reference.
#[display("{0}")]
Href(String),
/// Inline SVG content rendered verbatim.
#[display("{0}")]
Svg(String),
}
/// The source of an image node.
#[derive(Debug, Clone)]
pub struct ImageSource(ImageSourceInner);
impl Display for ImageSource {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
//
#[derive(Debug, Clone, Default)]
pub(crate) struct ImageMeta {
pub(crate) source: ImageSource,
pub(crate) width: f32,
pub(crate) height: f32,
pub(crate) cross_origin: Option<CrossOrigin>,
}
impl ImageMeta {
/// Creates a new [`ImageMeta`] instance.
///
/// # Arguments
/// - `source`: The [`ImageSource`] value.
/// - `width`: The intrinsic width of the image.
/// - `height`: The intrinsic height of the image.
///
/// # Returns
/// - [`Self`]
pub(crate) fn new<S>(source: S, width: f32, height: f32) -> Self
where
S: Into<ImageSource>,
{
Self {
source: source.into(),
width,
height,
..Default::default()
}
}
/// Computes the final size of the image.
///
/// # Note
/// Aspect ratio is preserved when only a single dimension is constrained.
///
/// # Arguments
/// - `known_dimensions`: The known layout dimensions provided by the
/// `taffy`.
///
/// # Returns
/// - The resolved image size.
pub(crate) fn measure(&mut self, known_dimensions: Size<Option<f32>>) -> Size<f32> {
match (known_dimensions.width, known_dimensions.height) {
(Some(width), Some(height)) => Size { width, height },
(Some(width), None) => Size {
width,
height: (width / self.width.max(1.0)) * self.height,
},
(None, Some(height)) => Size {
width: (height / self.height.max(1.0)) * self.width,
height,
},
(None, None) => Size {
width: self.width,
height: self.height,
},
}
}
/// Renders the image into the output stream.
///
/// # Arguments
/// - `ctx`: The current [`RenderContext`].
/// - `layout`: The computed layout for the image node.
pub(crate) fn render<W>(
&self,
ctx: &mut RenderContext<W>,
layout: taffy::Layout,
) -> std::fmt::Result
where
W: Write,
{
let Size { width, height } = layout.size;
match &self.source.inner() {
ImageSourceInner::Href(href) => ElementWriter::new(ctx.out, "image")?
.attr("href", href.as_str())?
.attrs([("width", width), ("height", height)])?
.attr("crossorigin", self.cross_origin.map(|x| (x,)))?
.close(),
ImageSourceInner::Svg(svg) => ctx.out.write_str(svg.as_str()),
}
}
}
impl ImageSource {
/// Creates an [`ImageSource`] referencing an external URL. The URL is
/// escaped before being embedded in the output.
///
/// # Arguments
/// - `url`: The image URL.
///
/// # Returns
/// - [`Self`]
pub fn href<S>(url: S) -> Self
where
S: AsRef<str>,
{
Self(ImageSourceInner::Href(escape(url.as_ref()).to_string()))
}
/// Creates an [`ImageSource`] from raw SVG markup. The SVG content is
/// emitted verbatim during rendering.
///
/// # Arguments
/// - `svg`: The SVG markup.
///
/// # Returns
/// - [`Self`]
pub fn svg<S>(svg: S) -> Self
where
S: AsRef<str>,
{
Self(ImageSourceInner::Svg(svg.as_ref().to_string()))
}
/// Returns the underlying image source representation.
fn inner(&self) -> &ImageSourceInner {
&self.0
}
}
impl Default for ImageSource {
fn default() -> Self {
ImageSource::href("")
}
}
impl<T> From<T> for ImageSource
where
T: AsRef<str>,
{
#[inline]
fn from(value: T) -> Self {
ImageSource::href(value.as_ref())
}
}