1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
7#![deny(clippy::trivially_copy_pass_by_ref)]
8
9mod text;
10
11use std::borrow::Cow;
12
13use cairo::{Context, Filter, Format, ImageSurface, Matrix, Rectangle, SurfacePattern};
14
15use piet::kurbo::{Affine, PathEl, Point, QuadBez, Rect, Shape, Size};
16use piet::{
17 Color, Error, FixedGradient, Image, ImageFormat, InterpolationMode, IntoBrush, LineCap,
18 LineJoin, RenderContext, StrokeStyle,
19};
20
21pub use cairo;
22
23pub use crate::text::{CairoText, CairoTextLayout, CairoTextLayoutBuilder};
24
25pub struct CairoRenderContext<'a> {
26 ctx: &'a Context,
29 text: CairoText,
30 transform_stack: Vec<Affine>,
35 error: Result<(), cairo::Error>,
36}
37
38#[derive(Clone)]
39pub enum Brush {
40 Solid(u32),
41 Linear(cairo::LinearGradient),
42 Radial(cairo::RadialGradient),
43}
44
45#[derive(Clone)]
46pub struct CairoImage(ImageSurface);
47
48macro_rules! set_gradient_stops {
51 ($dst: expr, $stops: expr) => {
52 for stop in $stops {
53 let rgba = stop.color.as_rgba_u32();
54 $dst.add_color_stop_rgba(
55 stop.pos as f64,
56 byte_to_frac(rgba >> 24),
57 byte_to_frac(rgba >> 16),
58 byte_to_frac(rgba >> 8),
59 byte_to_frac(rgba),
60 );
61 }
62 };
63}
64
65impl<'a> RenderContext for CairoRenderContext<'a> {
66 type Brush = Brush;
67
68 type Text = CairoText;
69 type TextLayout = CairoTextLayout;
70
71 type Image = CairoImage;
72
73 fn status(&mut self) -> Result<(), Error> {
74 match self.error {
75 Ok(_) => Ok(()),
76 Err(err) => Err(Error::BackendError(err.into())),
77 }
78 }
79
80 fn clear(&mut self, region: impl Into<Option<Rect>>, color: Color) {
81 let region: Option<Rect> = region.into();
82 let _ = self.with_save(|rc| {
83 rc.ctx.reset_clip();
84 if let Some(region) = region {
86 rc.transform(rc.current_transform().inverse());
87 rc.clip(region);
88 }
89
90 let rgba = color.as_rgba_u32();
92 rc.ctx.set_source_rgba(
93 byte_to_frac(rgba >> 24),
94 byte_to_frac(rgba >> 16),
95 byte_to_frac(rgba >> 8),
96 byte_to_frac(rgba),
97 );
98 rc.ctx.set_operator(cairo::Operator::Source);
99 rc.ctx.paint().map_err(convert_error)
100 });
101 }
102
103 fn solid_brush(&mut self, color: Color) -> Brush {
104 Brush::Solid(color.as_rgba_u32())
105 }
106
107 fn gradient(&mut self, gradient: impl Into<FixedGradient>) -> Result<Brush, Error> {
108 match gradient.into() {
109 FixedGradient::Linear(linear) => {
110 let (x0, y0) = (linear.start.x, linear.start.y);
111 let (x1, y1) = (linear.end.x, linear.end.y);
112 let lg = cairo::LinearGradient::new(x0, y0, x1, y1);
113 set_gradient_stops!(&lg, &linear.stops);
114 Ok(Brush::Linear(lg))
115 }
116 FixedGradient::Radial(radial) => {
117 let (xc, yc) = (radial.center.x, radial.center.y);
118 let (xo, yo) = (radial.origin_offset.x, radial.origin_offset.y);
119 let r = radial.radius;
120 let rg = cairo::RadialGradient::new(xc + xo, yc + yo, 0.0, xc, yc, r);
121 set_gradient_stops!(&rg, &radial.stops);
122 Ok(Brush::Radial(rg))
123 }
124 }
125 }
126
127 fn fill(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
128 let brush = brush.make_brush(self, || shape.bounding_box());
129 self.set_path(shape);
130 self.set_brush(&brush);
131 self.ctx.set_fill_rule(cairo::FillRule::Winding);
132 self.error = self.ctx.fill();
133 }
134
135 fn fill_even_odd(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>) {
136 let brush = brush.make_brush(self, || shape.bounding_box());
137 self.set_path(shape);
138 self.set_brush(&brush);
139 self.ctx.set_fill_rule(cairo::FillRule::EvenOdd);
140 self.error = self.ctx.fill();
141 }
142
143 fn clip(&mut self, shape: impl Shape) {
144 self.set_path(shape);
145 self.ctx.set_fill_rule(cairo::FillRule::Winding);
146 self.ctx.clip();
147 }
148
149 fn stroke(&mut self, shape: impl Shape, brush: &impl IntoBrush<Self>, width: f64) {
150 let brush = brush.make_brush(self, || shape.bounding_box());
151 self.set_path(shape);
152 self.set_stroke(width, None);
153 self.set_brush(&brush);
154 self.error = self.ctx.stroke();
155 }
156
157 fn stroke_styled(
158 &mut self,
159 shape: impl Shape,
160 brush: &impl IntoBrush<Self>,
161 width: f64,
162 style: &StrokeStyle,
163 ) {
164 let brush = brush.make_brush(self, || shape.bounding_box());
165 self.set_path(shape);
166 self.set_stroke(width, Some(style));
167 self.set_brush(&brush);
168 self.error = self.ctx.stroke();
169 }
170
171 fn text(&mut self) -> &mut Self::Text {
172 &mut self.text
173 }
174
175 fn draw_text(&mut self, layout: &Self::TextLayout, pos: impl Into<Point>) {
176 let pos = pos.into();
177 let offset = layout.pango_offset();
178 self.ctx.move_to(pos.x - offset.x, pos.y - offset.y);
179 pangocairo::functions::show_layout(self.ctx, layout.pango_layout());
180 }
181
182 fn save(&mut self) -> Result<(), Error> {
183 self.ctx.save().map_err(convert_error)?;
184 let state = self.transform_stack.last().copied().unwrap_or_default();
185 self.transform_stack.push(state);
186 Ok(())
187 }
188
189 fn restore(&mut self) -> Result<(), Error> {
190 if self.transform_stack.pop().is_some() {
191 self.ctx.restore().map_err(convert_error)
194 } else {
195 Err(Error::StackUnbalance)
196 }
197 }
198
199 fn finish(&mut self) -> Result<(), Error> {
200 self.ctx.target().flush();
201 Ok(())
202 }
203
204 fn transform(&mut self, transform: Affine) {
205 if let Some(last) = self.transform_stack.last_mut() {
206 *last *= transform;
207 } else {
208 self.transform_stack.push(transform);
209 }
210 self.ctx.transform(affine_to_matrix(transform));
211 }
212
213 fn current_transform(&self) -> Affine {
214 self.transform_stack.last().copied().unwrap_or_default()
215 }
216
217 #[allow(clippy::identity_op)]
219 fn make_image_with_stride(
220 &mut self,
221 width: usize,
222 height: usize,
223 stride: usize,
224 buf: &[u8],
225 format: ImageFormat,
226 ) -> Result<Self::Image, Error> {
227 let cairo_fmt = match format {
228 ImageFormat::Rgb | ImageFormat::Grayscale => Format::Rgb24,
229 ImageFormat::RgbaSeparate | ImageFormat::RgbaPremul => Format::ARgb32,
230 _ => return Err(Error::NotSupported),
231 };
232 let width_int = width as i32;
233 let height_int = height as i32;
234 let mut image = ImageSurface::create(cairo_fmt, width_int, height_int)
235 .map_err(|e| Error::BackendError(Box::new(e)))?;
236
237 if width_int == 0 || height_int == 0 {
239 return Ok(CairoImage(image));
240 }
241
242 let image_stride = image.stride() as usize;
244 {
245 if buf.len()
246 < piet::util::expected_image_buffer_size(
247 width * format.bytes_per_pixel(),
248 height,
249 stride,
250 )
251 {
252 return Err(Error::InvalidInput);
253 }
254
255 let mut data = image.data().map_err(|e| Error::BackendError(Box::new(e)))?;
256 for y in 0..height {
257 let src_off = y * stride;
258 let data = &mut data[y * image_stride..];
259 match format {
260 ImageFormat::Rgb => {
261 for x in 0..width {
262 write_rgb(
263 data,
264 x,
265 buf[src_off + x * 3 + 0],
266 buf[src_off + x * 3 + 1],
267 buf[src_off + x * 3 + 2],
268 );
269 }
270 }
271 ImageFormat::RgbaPremul => {
272 for x in 0..width {
276 write_rgba(
277 data,
278 x,
279 buf[src_off + x * 4 + 0],
280 buf[src_off + x * 4 + 1],
281 buf[src_off + x * 4 + 2],
282 buf[src_off + x * 4 + 3],
283 );
284 }
285 }
286 ImageFormat::RgbaSeparate => {
287 fn premul(x: u8, a: u8) -> u8 {
288 let y = (x as u16) * (a as u16);
289 ((y + (y >> 8) + 0x80) >> 8) as u8
290 }
291 for x in 0..width {
292 let a = buf[src_off + x * 4 + 3];
293 write_rgba(
294 data,
295 x,
296 premul(buf[src_off + x * 4 + 0], a),
297 premul(buf[src_off + x * 4 + 1], a),
298 premul(buf[src_off + x * 4 + 2], a),
299 a,
300 );
301 }
302 }
303 ImageFormat::Grayscale => {
304 for x in 0..width {
305 write_rgb(
306 data,
307 x,
308 buf[src_off + x],
309 buf[src_off + x],
310 buf[src_off + x],
311 );
312 }
313 }
314 _ => return Err(Error::NotSupported),
315 }
316 }
317 }
318 Ok(CairoImage(image))
319 }
320
321 #[inline]
322 fn draw_image(
323 &mut self,
324 image: &Self::Image,
325 dst_rect: impl Into<Rect>,
326 interp: InterpolationMode,
327 ) {
328 self.draw_image_inner(&image.0, None, dst_rect.into(), interp);
329 }
330
331 #[inline]
332 fn draw_image_area(
333 &mut self,
334 image: &Self::Image,
335 src_rect: impl Into<Rect>,
336 dst_rect: impl Into<Rect>,
337 interp: InterpolationMode,
338 ) {
339 self.draw_image_inner(&image.0, Some(src_rect.into()), dst_rect.into(), interp);
340 }
341
342 fn capture_image_area(&mut self, src_rect: impl Into<Rect>) -> Result<Self::Image, Error> {
343 let src_rect: Rect = src_rect.into();
344
345 let user_rect = Rectangle::new(
350 src_rect.x0,
351 src_rect.y0,
352 src_rect.width(),
353 src_rect.height(),
354 );
355 let device_rect = self.user_to_device(&user_rect);
356
357 let target_surface = ImageSurface::create(
359 Format::ARgb32,
360 device_rect.width() as i32,
361 device_rect.height() as i32,
362 )
363 .map_err(convert_error)?;
364 let target_ctx = Context::new(&target_surface).map_err(convert_error)?;
365
366 let cropped_source_surface = self
369 .ctx
370 .target()
371 .create_for_rectangle(device_rect)
372 .map_err(convert_error)?;
373
374 target_ctx
377 .set_source_surface(&cropped_source_surface, 0.0, 0.0)
378 .map_err(convert_error)?;
379 target_ctx.rectangle(0.0, 0.0, device_rect.width(), device_rect.height());
380 target_ctx.fill().map_err(convert_error)?;
381
382 Ok(CairoImage(target_surface))
383 }
384
385 fn blurred_rect(&mut self, rect: Rect, blur_radius: f64, brush: &impl IntoBrush<Self>) {
386 let brush = brush.make_brush(self, || rect);
387 match compute_blurred_rect(rect, blur_radius) {
388 Ok((image, origin)) => {
389 self.set_brush(&brush);
390 self.error = self.ctx.mask_surface(&image, origin.x, origin.y);
391 }
392 Err(err) => self.error = Err(err),
393 }
394 }
395}
396
397impl<'a> IntoBrush<CairoRenderContext<'a>> for Brush {
398 fn make_brush<'b>(
399 &'b self,
400 _piet: &mut CairoRenderContext,
401 _bbox: impl FnOnce() -> Rect,
402 ) -> std::borrow::Cow<'b, Brush> {
403 Cow::Borrowed(self)
404 }
405}
406
407impl Image for CairoImage {
408 fn size(&self) -> Size {
409 Size::new(self.0.width().into(), self.0.height().into())
410 }
411}
412
413impl<'a> CairoRenderContext<'a> {
414 pub fn new(ctx: &Context) -> CairoRenderContext<'_> {
420 CairoRenderContext {
421 ctx,
422 text: CairoText::new(),
423 transform_stack: Vec::new(),
424 error: Ok(()),
425 }
426 }
427
428 fn set_brush(&mut self, brush: &Brush) {
433 match *brush {
434 Brush::Solid(rgba) => self.ctx.set_source_rgba(
435 byte_to_frac(rgba >> 24),
436 byte_to_frac(rgba >> 16),
437 byte_to_frac(rgba >> 8),
438 byte_to_frac(rgba),
439 ),
440 Brush::Linear(ref linear) => self.error = self.ctx.set_source(linear),
441 Brush::Radial(ref radial) => self.error = self.ctx.set_source(radial),
442 }
443 }
444
445 fn set_stroke(&mut self, width: f64, style: Option<&StrokeStyle>) {
447 let default_style = StrokeStyle::default();
448 let style = style.unwrap_or(&default_style);
449
450 self.ctx.set_line_width(width);
451 self.ctx.set_line_join(convert_line_join(style.line_join));
452 self.ctx.set_line_cap(convert_line_cap(style.line_cap));
453
454 if let Some(limit) = style.miter_limit() {
455 self.ctx.set_miter_limit(limit);
456 }
457 self.ctx.set_dash(&style.dash_pattern, style.dash_offset);
458 }
459
460 fn set_path(&mut self, shape: impl Shape) {
461 self.ctx.new_path();
464 let mut last = Point::ZERO;
465 for el in shape.path_elements(1e-3) {
466 match el {
467 PathEl::MoveTo(p) => {
468 self.ctx.move_to(p.x, p.y);
469 last = p;
470 }
471 PathEl::LineTo(p) => {
472 self.ctx.line_to(p.x, p.y);
473 last = p;
474 }
475 PathEl::QuadTo(p1, p2) => {
476 let q = QuadBez::new(last, p1, p2);
477 let c = q.raise();
478 self.ctx
479 .curve_to(c.p1.x, c.p1.y, c.p2.x, c.p2.y, p2.x, p2.y);
480 last = p2;
481 }
482 PathEl::CurveTo(p1, p2, p3) => {
483 self.ctx.curve_to(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
484 last = p3;
485 }
486 PathEl::ClosePath => self.ctx.close_path(),
487 }
488 }
489 }
490
491 fn draw_image_inner(
492 &mut self,
493 image: &ImageSurface,
494 src_rect: Option<Rect>,
495 dst_rect: Rect,
496 interp: InterpolationMode,
497 ) {
498 let src_rect = match src_rect {
499 Some(src_rect) => src_rect,
500 None => Size::new(image.width() as f64, image.height() as f64).to_rect(),
501 };
502 if src_rect.is_zero_area() || dst_rect.is_zero_area() {
505 return;
506 }
507
508 let _ = self.with_save(|rc| {
509 let surface_pattern = SurfacePattern::create(image);
510 let filter = match interp {
511 InterpolationMode::NearestNeighbor => Filter::Nearest,
512 InterpolationMode::Bilinear => Filter::Bilinear,
513 };
514 surface_pattern.set_filter(filter);
515 let scale_x = dst_rect.width() / src_rect.width();
516 let scale_y = dst_rect.height() / src_rect.height();
517 rc.clip(dst_rect);
518 rc.ctx.translate(
519 dst_rect.x0 - scale_x * src_rect.x0,
520 dst_rect.y0 - scale_y * src_rect.y0,
521 );
522 rc.ctx.scale(scale_x, scale_y);
523 rc.error = rc.ctx.set_source(&surface_pattern);
524 rc.error = rc.ctx.paint();
525 Ok(())
526 });
527 }
528
529 fn user_to_device(&self, user_rect: &Rectangle) -> Rectangle {
530 let (x, y) = self.ctx.user_to_device(user_rect.x(), user_rect.y());
531 let (width, height) = self
532 .ctx
533 .user_to_device(user_rect.width(), user_rect.height());
534
535 Rectangle::new(x, y, width, height)
536 }
537}
538
539fn convert_line_cap(line_cap: LineCap) -> cairo::LineCap {
540 match line_cap {
541 LineCap::Butt => cairo::LineCap::Butt,
542 LineCap::Round => cairo::LineCap::Round,
543 LineCap::Square => cairo::LineCap::Square,
544 }
545}
546
547fn convert_line_join(line_join: LineJoin) -> cairo::LineJoin {
548 match line_join {
549 LineJoin::Miter { .. } => cairo::LineJoin::Miter,
550 LineJoin::Round => cairo::LineJoin::Round,
551 LineJoin::Bevel => cairo::LineJoin::Bevel,
552 }
553}
554
555fn byte_to_frac(byte: u32) -> f64 {
556 ((byte & 255) as f64) * (1.0 / 255.0)
557}
558
559fn affine_to_matrix(affine: Affine) -> Matrix {
561 let a = affine.as_coeffs();
562
563 Matrix::new(a[0], a[1], a[2], a[3], a[4], a[5])
564}
565
566fn compute_blurred_rect(rect: Rect, radius: f64) -> Result<(ImageSurface, Point), cairo::Error> {
567 let size = piet::util::size_for_blurred_rect(rect, radius);
568 match ImageSurface::create(Format::A8, size.width as i32, size.height as i32) {
569 Ok(mut image) => {
570 let stride = image.stride() as usize;
571 let mut data = image.data().unwrap();
578 let rect_exp = piet::util::compute_blurred_rect(rect, radius, stride, &mut data);
579 std::mem::drop(data);
580 let origin = rect_exp.origin();
581 Ok((image, origin))
582 }
583 Err(err) => Err(err),
584 }
585}
586
587fn convert_error(err: cairo::Error) -> Error {
588 Error::BackendError(err.into())
589}
590
591fn write_rgba(data: &mut [u8], column: usize, r: u8, g: u8, b: u8, a: u8) {
592 let (a, r, g, b) = (u32::from(a), u32::from(r), u32::from(g), u32::from(b));
596 let pixel = a << 24 | r << 16 | g << 8 | b;
597
598 data[4 * column..4 * (column + 1)].copy_from_slice(&pixel.to_ne_bytes());
599}
600
601fn write_rgb(data: &mut [u8], column: usize, r: u8, g: u8, b: u8) {
602 write_rgba(data, column, r, g, b, 0);
605}