euv_chart/backend/canvas2d/
impl.rs1use super::*;
2
3impl Canvas2dChartBackend {
5 pub fn create(canvas: &HtmlCanvasElement) -> Option<Self> {
18 let context: CanvasRenderingContext2d = canvas
19 .get_context("2d")
20 .ok()
21 .flatten()?
22 .dyn_into::<CanvasRenderingContext2d>()
23 .ok()?;
24 Some(Self {
25 canvas: canvas.clone(),
26 context,
27 dpr: 1.0,
28 width: 0.0,
29 height: 0.0,
30 })
31 }
32
33 pub fn canvas(&self) -> &HtmlCanvasElement {
39 self.get_canvas()
40 }
41
42 pub fn dpr(&self) -> f64 {
48 self.get_dpr()
49 }
50
51 pub(crate) fn trace_path(&self, path: &Path) {
57 self.get_context().begin_path();
58 for command in path.commands() {
59 match *command {
60 PathCommand::MoveTo(x, y) => self.get_context().move_to(x as f64, y as f64),
61 PathCommand::LineTo(x, y) => self.get_context().line_to(x as f64, y as f64),
62 PathCommand::QuadTo(cx, cy, x, y) => self
63 .get_context()
64 .quadratic_curve_to(cx as f64, cy as f64, x as f64, y as f64),
65 PathCommand::CubicTo(c1x, c1y, c2x, c2y, x, y) => {
66 self.get_context().bezier_curve_to(
67 c1x as f64, c1y as f64, c2x as f64, c2y as f64, x as f64, y as f64,
68 )
69 }
70 PathCommand::Close => self.get_context().close_path(),
71 }
72 }
73 }
74
75 pub(crate) fn trace_strip(&self, points: &[Point], baseline: f32) {
82 self.get_context().begin_path();
83 if let Some(first) = points.first() {
84 self.get_context().move_to(first.x as f64, first.y as f64);
85 for point in &points[1..] {
86 self.get_context().line_to(point.x as f64, point.y as f64);
87 }
88 self.get_context()
89 .line_to(points[points.len() - 1].x as f64, baseline as f64);
90 self.get_context()
91 .line_to(points[0].x as f64, baseline as f64);
92 self.get_context().close_path();
93 }
94 }
95
96 pub(crate) fn apply_fill(&self, paint: &Fill) {
102 match paint {
103 Fill::Solid(color) => {
104 self.get_context().set_fill_style_str(&color.to_css());
105 }
106 Fill::Linear(gradient) => {
107 if let Some(canvas) = self.create_gradient(gradient) {
108 self.get_context().set_fill_style_canvas_gradient(&canvas);
109 }
110 }
111 Fill::Radial(gradient) => {
112 if let Some(canvas) = self.create_radial_gradient(gradient) {
113 self.get_context().set_fill_style_canvas_gradient(&canvas);
114 }
115 }
116 Fill::None => {}
117 }
118 }
119
120 pub(crate) fn apply_stroke(&self, paint: &Stroke) {
126 match paint {
127 Stroke::Solid(color) => {
128 self.get_context().set_stroke_style_str(&color.to_css());
129 }
130 Stroke::Linear(gradient) => {
131 if let Some(canvas) = self.create_gradient(gradient) {
132 self.get_context().set_stroke_style_canvas_gradient(&canvas);
133 }
134 }
135 Stroke::Radial(gradient) => {
136 if let Some(canvas) = self.create_radial_gradient(gradient) {
137 self.get_context().set_stroke_style_canvas_gradient(&canvas);
138 }
139 }
140 Stroke::None => {}
141 }
142 }
143
144 fn create_gradient(&self, gradient: &LinearGradient) -> Option<CanvasGradient> {
154 let canvas: CanvasGradient = self.get_context().create_linear_gradient(
155 gradient.start.0,
156 gradient.start.1,
157 gradient.end.0,
158 gradient.end.1,
159 );
160 for stop in &gradient.stops {
161 let _: Result<(), JsValue> =
162 canvas.add_color_stop(stop.offset as f32, &stop.color.to_css());
163 }
164 Some(canvas)
165 }
166
167 fn create_radial_gradient(&self, gradient: &RadialGradient) -> Option<CanvasGradient> {
177 let canvas: CanvasGradient = self
178 .get_context()
179 .create_radial_gradient(
180 gradient.center.0,
181 gradient.center.1,
182 0.0,
183 gradient.center.0,
184 gradient.center.1,
185 gradient.radius,
186 )
187 .ok()?;
188 for stop in &gradient.stops {
189 let _: Result<(), JsValue> =
190 canvas.add_color_stop(stop.offset as f32, &stop.color.to_css());
191 }
192 Some(canvas)
193 }
194}
195
196impl ChartBackend for Canvas2dChartBackend {
198 fn kind(&self) -> BackendKind {
204 BackendKind::Canvas2D
205 }
206
207 fn resize(&mut self, width: f64, height: f64, dpr: f64) {
213 self.set_dpr(dpr);
214 self.set_width(width);
215 self.set_height(height);
216 let physical_width: u32 = (width * dpr).round().max(1.0) as u32;
217 let physical_height: u32 = (height * dpr).round().max(1.0) as u32;
218 if self.get_canvas().width() != physical_width {
219 self.get_canvas().set_width(physical_width);
220 }
221 if self.get_canvas().height() != physical_height {
222 self.get_canvas().set_height(physical_height);
223 }
224 let _: Result<(), JsValue> = self
227 .get_context()
228 .set_transform(dpr, 0.0, 0.0, dpr, 0.0, 0.0);
229 self.get_context().set_line_join("miter");
230 }
231
232 fn render(&mut self, list: &DisplayList, shaper: &mut TextShaper) {
239 let _: Result<(), JsValue> =
240 self.get_context()
241 .set_transform(self.get_dpr(), 0.0, 0.0, self.get_dpr(), 0.0, 0.0);
242 self.get_context()
243 .clear_rect(0.0, 0.0, self.get_width(), self.get_height());
244 for item in list.items() {
245 match &item.cmd {
246 DrawCmd::Fill {
247 path,
248 paint,
249 opacity,
250 } => {
251 if matches!(paint, Fill::None) {
252 continue;
253 }
254 self.get_context().save();
255 self.get_context().set_global_alpha(*opacity);
256 self.apply_fill(paint);
257 self.trace_path(path);
258 self.get_context().fill();
259 self.get_context().restore();
260 }
261 DrawCmd::Stroke {
262 path,
263 paint,
264 style,
265 opacity,
266 } => {
267 if matches!(paint, Stroke::None) || style.width <= 0.0 {
268 continue;
269 }
270 self.get_context().save();
271 self.get_context()
272 .set_global_alpha(*opacity * style.opacity);
273 self.apply_stroke(paint);
274 self.get_context().set_line_width(style.width);
275 self.get_context().set_line_cap(line_cap_name(style.cap));
276 self.get_context().set_line_join(line_join_name(style.join));
277 if !style.dash.is_solid() {
278 let dash: Array = Array::new();
279 for length in &style.dash.pattern {
280 dash.push(&JsValue::from_f64(*length as f64));
281 }
282 let _: Result<(), JsValue> =
283 self.get_context().set_line_dash(dash.as_ref());
284 self.get_context()
285 .set_line_dash_offset(style.dash.offset as f64);
286 } else {
287 let _: Result<(), JsValue> =
288 self.get_context().set_line_dash(Array::new().as_ref());
289 }
290 self.trace_path(path);
291 self.get_context().stroke();
292 self.get_context().restore();
293 }
294 DrawCmd::Strip {
295 points,
296 baseline,
297 paint,
298 opacity,
299 } => {
300 if matches!(paint, Fill::None) || points.len() < 2 {
301 continue;
302 }
303 self.get_context().save();
304 self.get_context().set_global_alpha(*opacity);
305 self.apply_fill(paint);
306 self.trace_strip(points, *baseline);
307 self.get_context().fill();
308 self.get_context().restore();
309 }
310 DrawCmd::Text { text, x, y, style } => {
311 let run: TextRun = shaper.shape(text, style, *x, *y);
315 self.get_context().save();
316 self.get_context().set_font(&style.font());
317 self.get_context().set_text_align("left");
318 self.get_context().set_text_baseline("alphabetic");
319 for line in &run.lines {
320 if let Some(background) = style.background {
321 self.get_context().set_fill_style_str(&background.to_css());
322 self.get_context().fill_rect(
323 line.left - style.padding.3,
324 line.baseline - run.ascent - style.padding.0,
325 line.width + style.padding.1 + style.padding.3,
326 run.ascent + run.descent + style.padding.0 + style.padding.2,
327 );
328 }
329 self.get_context().set_fill_style_str(&style.color.to_css());
330 let _: Result<(), JsValue> =
331 self.get_context()
332 .fill_text(&line.text, line.left, line.baseline);
333 }
334 self.get_context().restore();
335 }
336 DrawCmd::Image {
337 image,
338 x,
339 y,
340 width,
341 height,
342 } => {
343 let _: Result<(), JsValue> = self
344 .get_context()
345 .draw_image_with_html_image_element_and_dw_and_dh(
346 image, *x, *y, *width, *height,
347 );
348 }
349 DrawCmd::Clip { path } => {
350 self.get_context().save();
351 self.trace_path(path);
352 self.get_context().clip();
353 }
354 DrawCmd::PopClip => {
355 self.get_context().restore();
356 }
357 }
358 }
359 }
360
361 fn dispose(&mut self) {
363 let _: Result<(), JsValue> = self
364 .get_context()
365 .set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
366 self.get_context()
367 .clear_rect(0.0, 0.0, self.get_width(), self.get_height());
368 }
369}