diff --git a/piet-cairo/Cargo.toml b/piet-cairo/Cargo.toml index 2293238c..ad0d5dbf 100644 --- a/piet-cairo/Cargo.toml +++ b/piet-cairo/Cargo.toml @@ -13,17 +13,17 @@ categories = ["rendering::graphics-api"] [dependencies] piet = { version = "0.4.0", path = "../piet" } -cairo-rs = { version = "0.9.1", default-features = false } # We don't need glib -pango = { version = "0.9.1", features = ["v1_44"] } -pango-sys = { version = "0.10.0", features = ["v1_44"] } -pangocairo = "0.10.0" -glib = "0.10.3" +cairo-rs = { version = "0.14.0", default-features = false } # We don't need glib +pango = { version = "0.14.0", features = ["v1_44"] } +pango-sys = { version = "0.14.0", features = ["v1_44"] } +pangocairo = "0.14.0" +glib = "0.14.0" unicode-segmentation = "1.3.0" xi-unicode = "0.3.0" [dev-dependencies] piet = { version = "0.4.0", path = "../piet", features = ["samples"] } -cairo-rs = { version = "0.9.1", default-features = false, features = ["png"] } +cairo-rs = { version = "0.14.0", default-features = false, features = ["png"] } criterion = "0.3" [[bench]] diff --git a/piet-cairo/benches/make_image.rs b/piet-cairo/benches/make_image.rs index f4eff2b3..696b1be2 100644 --- a/piet-cairo/benches/make_image.rs +++ b/piet-cairo/benches/make_image.rs @@ -38,7 +38,7 @@ pub fn bench_make_image(c: &mut Criterion) { c.bench_function(&format!("make_image_{}_{:?}", name, format), |b| { let unused_surface = ImageSurface::create(Format::ARgb32, 1, 1).expect("Can't create surface"); - let cr = Context::new(&unused_surface); + let cr = Context::new(&unused_surface).unwrap(); let mut piet_context = CairoRenderContext::new(&cr); let width = black_box(width.try_into().unwrap()); diff --git a/piet-cairo/examples/test-picture.rs b/piet-cairo/examples/test-picture.rs index 8895273f..18d59690 100644 --- a/piet-cairo/examples/test-picture.rs +++ b/piet-cairo/examples/test-picture.rs @@ -26,7 +26,7 @@ fn run_sample(idx: usize, base_dir: &Path) -> Result<(), Box { // by cairo. Instead we maintain our own stack, which will contain // only those transforms applied by us. transform_stack: Vec, + error: Result<(), cairo::Error>, } impl<'a> CairoRenderContext<'a> {} @@ -65,7 +66,10 @@ impl<'a> RenderContext for CairoRenderContext<'a> { type Image = CairoImage; fn status(&mut self) -> Result<(), Error> { - Ok(()) + match self.error { + Ok(_) => Ok(()), + Err(err) => Err(Error::BackendError(err.into())), + } } fn clear(&mut self, region: impl Into>, color: Color) { @@ -87,8 +91,7 @@ impl<'a> RenderContext for CairoRenderContext<'a> { byte_to_frac(rgba), ); rc.ctx.set_operator(cairo::Operator::Source); - rc.ctx.paint(); - Ok(()) + rc.ctx.paint().map_err(convert_error) }); } @@ -121,7 +124,7 @@ impl<'a> RenderContext for CairoRenderContext<'a> { self.set_path(shape); self.set_brush(&*brush); self.ctx.set_fill_rule(cairo::FillRule::Winding); - self.ctx.fill(); + self.error = self.ctx.fill(); } fn fill_even_odd(&mut self, shape: impl Shape, brush: &impl IntoBrush) { @@ -129,7 +132,7 @@ impl<'a> RenderContext for CairoRenderContext<'a> { self.set_path(shape); self.set_brush(&*brush); self.ctx.set_fill_rule(cairo::FillRule::EvenOdd); - self.ctx.fill(); + self.error = self.ctx.fill(); } fn clip(&mut self, shape: impl Shape) { @@ -143,7 +146,7 @@ impl<'a> RenderContext for CairoRenderContext<'a> { self.set_path(shape); self.set_stroke(width, None); self.set_brush(&*brush); - self.ctx.stroke(); + self.error = self.ctx.stroke(); } fn stroke_styled( @@ -157,7 +160,7 @@ impl<'a> RenderContext for CairoRenderContext<'a> { self.set_path(shape); self.set_stroke(width, Some(style)); self.set_brush(&*brush); - self.ctx.stroke(); + self.error = self.ctx.stroke(); } fn text(&mut self) -> &mut Self::Text { @@ -168,30 +171,29 @@ impl<'a> RenderContext for CairoRenderContext<'a> { let pos = pos.into(); let offset = layout.pango_offset(); self.ctx.move_to(pos.x - offset.x, pos.y - offset.y); - pangocairo::show_layout(&self.ctx, layout.pango_layout()); + pangocairo::show_layout(self.ctx, layout.pango_layout()); } fn save(&mut self) -> Result<(), Error> { - self.ctx.save(); + self.ctx.save().map_err(convert_error)?; let state = self.transform_stack.last().copied().unwrap_or_default(); self.transform_stack.push(state); - self.status() + Ok(()) } fn restore(&mut self) -> Result<(), Error> { if self.transform_stack.pop().is_some() { // we're defensive about calling restore on the inner context, // because an unbalanced call will trigger a panic in cairo-rs - self.ctx.restore(); - self.status() + self.ctx.restore().map_err(convert_error) } else { Err(Error::StackUnbalance) } } fn finish(&mut self) -> Result<(), Error> { - self.ctx.get_target().flush(); - self.status() + self.ctx.target().flush(); + Ok(()) } fn transform(&mut self, transform: Affine) { @@ -234,11 +236,9 @@ impl<'a> RenderContext for CairoRenderContext<'a> { // Confident no borrow errors because we just created it. let bytes_per_pixel = format.bytes_per_pixel(); let bytes_per_row = width * bytes_per_pixel; - let stride = image.get_stride() as usize; + let stride = image.stride() as usize; { - let mut data = image - .get_data() - .map_err(|e| Error::BackendError(Box::new(e)))?; + let mut data = image.data().map_err(|e| Error::BackendError(Box::new(e)))?; for y in 0..height { let src_off = y * bytes_per_row; let data = &mut data[y * stride..]; @@ -331,9 +331,16 @@ impl<'a> RenderContext for CairoRenderContext<'a> { fn blurred_rect(&mut self, rect: Rect, blur_radius: f64, brush: &impl IntoBrush) { let brush = brush.make_brush(self, || rect); - let (image, origin) = compute_blurred_rect(rect, blur_radius); - self.set_brush(&*brush); - self.ctx.mask_surface(&image, origin.x, origin.y); + match compute_blurred_rect(rect, blur_radius) { + Ok((image, origin)) => { + self.set_brush(&*brush); + self.error = self + .ctx + .mask_surface(&image, origin.x, origin.y) + .map_err(cairo::Error::into); + } + Err(err) => self.error = Err(err), + } } } @@ -349,7 +356,7 @@ impl<'a> IntoBrush> for Brush { impl Image for CairoImage { fn size(&self) -> Size { - Size::new(self.0.get_width().into(), self.0.get_height().into()) + Size::new(self.0.width().into(), self.0.height().into()) } } @@ -364,6 +371,7 @@ impl<'a> CairoRenderContext<'a> { ctx, text: CairoText::new(), transform_stack: Vec::new(), + error: Ok(()), } } @@ -379,8 +387,8 @@ impl<'a> CairoRenderContext<'a> { byte_to_frac(rgba >> 8), byte_to_frac(rgba), ), - Brush::Linear(ref linear) => self.ctx.set_source(linear), - Brush::Radial(ref radial) => self.ctx.set_source(radial), + Brush::Linear(ref linear) => self.error = self.ctx.set_source(linear), + Brush::Radial(ref radial) => self.error = self.ctx.set_source(radial), } } @@ -439,7 +447,7 @@ impl<'a> CairoRenderContext<'a> { ) { let src_rect = match src_rect { Some(src_rect) => src_rect, - None => Size::new(image.get_width() as f64, image.get_height() as f64).to_rect(), + None => Size::new(image.width() as f64, image.height() as f64).to_rect(), }; // Cairo returns an error if we try to paint an empty image, causing us to panic. We check if // either the source or destination is empty, and early-return if so. @@ -462,8 +470,8 @@ impl<'a> CairoRenderContext<'a> { dst_rect.y0 - scale_y * src_rect.y0, ); rc.ctx.scale(scale_x, scale_y); - rc.ctx.set_source(&surface_pattern); - rc.ctx.paint(); + rc.error = rc.ctx.set_source(&surface_pattern); + rc.error = rc.ctx.paint(); Ok(()) }); } @@ -503,17 +511,29 @@ fn affine_to_matrix(affine: Affine) -> Matrix { } } -fn compute_blurred_rect(rect: Rect, radius: f64) -> (ImageSurface, Point) { +fn compute_blurred_rect(rect: Rect, radius: f64) -> Result<(ImageSurface, Point), cairo::Error> { let size = piet::util::size_for_blurred_rect(rect, radius); - // TODO: maybe not panic on error (but likely to happen only in extreme cases such as OOM) - let mut image = - ImageSurface::create(Format::A8, size.width as i32, size.height as i32).unwrap(); - let stride = image.get_stride() as usize; - let mut data = image.get_data().unwrap(); - let rect_exp = piet::util::compute_blurred_rect(rect, radius, stride, &mut *data); - std::mem::drop(data); - let origin = rect_exp.origin(); - (image, origin) + match ImageSurface::create(Format::A8, size.width as i32, size.height as i32) { + Ok(mut image) => { + let stride = image.stride() as usize; + // An error is returned when either: + // The reference to image is dropped (it isnt since its still in scope), + // There is an error on image (there isnt since we havnt used it yet), + // The pointer to the image is null aka the surface isnt an imagesurface (it is an imagesurface), + // Or the surface is finished (it isnt, we know because we dont finish it). + // Since we know none of these cases should happen, we know that this should not panic. + let mut data = image.data().unwrap(); + let rect_exp = piet::util::compute_blurred_rect(rect, radius, stride, &mut *data); + std::mem::drop(data); + let origin = rect_exp.origin(); + Ok((image, origin)) + } + Err(err) => Err(err), + } +} + +fn convert_error(err: cairo::Error) -> Error { + Error::BackendError(err.into()) } fn write_rgba(data: &mut [u8], column: usize, r: u8, g: u8, b: u8, a: u8) { diff --git a/piet-cairo/src/text.rs b/piet-cairo/src/text.rs index 1f842a62..5666cee3 100644 --- a/piet-cairo/src/text.rs +++ b/piet-cairo/src/text.rs @@ -7,7 +7,8 @@ use std::rc::Rc; use glib::translate::{from_glib_full, ToGlibPtr}; -use pango::{AttrList, FontMapExt}; +use pango::prelude::FontMapExt; +use pango::AttrList; use pango_sys::pango_attr_insert_hyphens_new; use pangocairo::FontMap; @@ -62,7 +63,7 @@ struct AttributeWithRange { } impl AttributeWithRange { - fn into_pango(self) -> Option { + fn into_pango(self) -> PangoAttribute { let mut pango_attribute = match &self.attribute { TextAttribute::FontFamily(family) => { let family = family.name(); @@ -70,12 +71,12 @@ impl AttributeWithRange { * NOTE: If the family fails to resolve we just don't apply the attribute. * That allows Pango to use its default font of choice to render that text */ - PangoAttribute::new_family(family)? + PangoAttribute::new_family(family) } TextAttribute::FontSize(size) => { let size = (size * PANGO_SCALE) as i32; - PangoAttribute::new_size_absolute(size).unwrap() + PangoAttribute::new_size_absolute(size) } TextAttribute::Weight(weight) => { @@ -106,7 +107,7 @@ impl AttributeWithRange { } } - PangoAttribute::new_weight(pango_weights[closest_index].1).unwrap() + PangoAttribute::new_weight(pango_weights[closest_index].1) } TextAttribute::TextColor(text_color) => { @@ -116,7 +117,6 @@ impl AttributeWithRange { (g as u16 * 256) + (g as u16), (b as u16 * 256) + (b as u16), ) - .unwrap() } TextAttribute::Style(style) => { @@ -124,7 +124,7 @@ impl AttributeWithRange { FontStyle::Regular => PangoStyle::Normal, FontStyle::Italic => PangoStyle::Italic, }; - PangoAttribute::new_style(style).unwrap() + PangoAttribute::new_style(style) } &TextAttribute::Underline(underline) => { @@ -133,11 +133,11 @@ impl AttributeWithRange { } else { PangoUnderline::None }; - PangoAttribute::new_underline(underline).unwrap() + PangoAttribute::new_underline(underline) } &TextAttribute::Strikethrough(strikethrough) => { - PangoAttribute::new_strikethrough(strikethrough).unwrap() + PangoAttribute::new_strikethrough(strikethrough) } }; @@ -146,7 +146,7 @@ impl AttributeWithRange { pango_attribute.set_end_index(range.end.try_into().unwrap()); } - Some(pango_attribute) + pango_attribute } } @@ -154,7 +154,7 @@ impl CairoText { /// Create a new factory that satisfies the piet `Text` trait. #[allow(clippy::new_without_default)] pub fn new() -> CairoText { - let fontmap = FontMap::get_default().unwrap(); + let fontmap = FontMap::default().unwrap(); CairoText { pango_context: fontmap.create_context().unwrap(), } @@ -278,59 +278,54 @@ impl TextLayoutBuilder for CairoTextLayoutBuilder { fn build(self) -> Result { let pango_attributes = AttrList::new(); - let add_attribute = |attribute| { - if let Some(attribute) = attribute { - pango_attributes.insert(attribute); - } - }; if let Some(attr) = unsafe { from_glib_full(pango_attr_insert_hyphens_new(0)) } { pango_attributes.insert(attr); } - add_attribute( + pango_attributes.insert( AttributeWithRange { attribute: TextAttribute::FontFamily(self.defaults.font), range: None, } .into_pango(), ); - add_attribute( + pango_attributes.insert( AttributeWithRange { attribute: TextAttribute::FontSize(self.defaults.font_size), range: None, } .into_pango(), ); - add_attribute( + pango_attributes.insert( AttributeWithRange { attribute: TextAttribute::Weight(self.defaults.weight), range: None, } .into_pango(), ); - add_attribute( + pango_attributes.insert( AttributeWithRange { attribute: TextAttribute::TextColor(self.defaults.fg_color), range: None, } .into_pango(), ); - add_attribute( + pango_attributes.insert( AttributeWithRange { attribute: TextAttribute::Style(self.defaults.style), range: None, } .into_pango(), ); - add_attribute( + pango_attributes.insert( AttributeWithRange { attribute: TextAttribute::Underline(self.defaults.underline), range: None, } .into_pango(), ); - add_attribute( + pango_attributes.insert( AttributeWithRange { attribute: TextAttribute::Strikethrough(self.defaults.strikethrough), range: None, @@ -339,7 +334,7 @@ impl TextLayoutBuilder for CairoTextLayoutBuilder { ); for attribute in self.attributes { - add_attribute(attribute.into_pango()); + pango_attributes.insert(attribute.into_pango()); } self.pango_layout.set_attributes(Some(&pango_attributes)); @@ -415,43 +410,38 @@ impl TextLayout for CairoTextLayout { let line = self .pango_layout - .get_line(line_number.try_into().unwrap()) + .line(line_number.try_into().unwrap()) .unwrap(); let line_text = self.line_text(line_number).unwrap(); let line_start_idx = self.line_metric(line_number).unwrap().start_offset; - // pango-rs uses an option here when it should return a (bool, i32, i32); - // this is an error on their part, I think - // FIXME: when https://github.com/gtk-rs/gtk-rs/pull/375 is released - // we can improve this. - let (rel_idx, is_inside_x) = match line.x_to_index(x as i32) { - Some((idx, trailing)) => { - let idx = idx as usize - line_start_idx; - let trailing_len: usize = (&line_text[idx..]) - .chars() - .take(trailing as usize) - .map(char::len_utf8) - .sum(); - (idx + trailing_len, true) - } - None => { - let hit_is_left = x <= 0; - let hard_break_len = match line_text.as_bytes() { - [.., b'\r', b'\n'] => 2, - [.., b'\n'] => 1, - _ => 0, - }; - let idx = if hit_is_left == self.is_rtl { - line_text.len().saturating_sub(hard_break_len) - } else { - 0 - }; - (idx, false) + + let hitpos = line.x_to_index(x as i32); + let rel_idx = if hitpos.is_inside { + let idx = hitpos.index as usize - line_start_idx; + let trailing_len: usize = (&line_text[idx..]) + .chars() + .take(hitpos.trailing as usize) + .map(char::len_utf8) + .sum(); + idx + trailing_len + } else { + let hit_is_left = x <= 0; + let hard_break_len = match line_text.as_bytes() { + [.., b'\r', b'\n'] => 2, + [.., b'\n'] => 1, + _ => 0, + }; + if hit_is_left == self.is_rtl { + line_text.len().saturating_sub(hard_break_len) + } else { + 0 } }; + let is_inside_y = point.y >= 0. && point.y <= self.size.height; - HitTestPoint::new(line_start_idx + rel_idx, is_inside_x && is_inside_y) + HitTestPoint::new(line_start_idx + rel_idx, hitpos.is_inside && is_inside_y) } fn hit_test_text_position(&self, idx: usize) -> HitTestPosition { @@ -515,9 +505,9 @@ impl CairoTextLayout { let mut y_offset = 0.; let mut widest_logical_width = 0; let mut widest_whitespaceless_width = 0; - let mut iterator = self.pango_layout.get_iter().unwrap(); + let mut iterator = self.pango_layout.iter().unwrap(); loop { - let line = iterator.get_line_readonly().unwrap(); + let line = iterator.line_readonly().unwrap(); //FIXME: replace this when pango 0.10.0 lands let (start_offset, end_offset) = unsafe { @@ -536,7 +526,7 @@ impl CairoTextLayout { _ => end_offset, }; - let logical_rect = iterator.get_line_extents().1; + let logical_rect = iterator.line_extents().1; if logical_rect.width > widest_logical_width { widest_logical_width = logical_rect.width; } @@ -559,7 +549,7 @@ impl CairoTextLayout { start_offset, end_offset, trailing_whitespace, - baseline: (iterator.get_baseline() as f64 / PANGO_SCALE) - y_offset, + baseline: (iterator.baseline() as f64 / PANGO_SCALE) - y_offset, height: logical_rect.height as f64 / PANGO_SCALE, y_offset, }); @@ -574,7 +564,7 @@ impl CairoTextLayout { self.line_metrics = line_metrics.into(); self.x_offsets = x_offsets.into(); - let (ink_extent, logical_extent) = self.pango_layout.get_extents(); + let (ink_extent, logical_extent) = self.pango_layout.extents(); let ink_extent = to_kurbo_rect(ink_extent); let logical_extent = to_kurbo_rect(logical_extent); diff --git a/piet-common/Cargo.toml b/piet-common/Cargo.toml index 3148d32c..7c454398 100644 --- a/piet-common/Cargo.toml +++ b/piet-common/Cargo.toml @@ -40,8 +40,8 @@ png = { version = "0.16.1", optional = true } [target.'cfg(target_os="linux")'.dependencies] piet-cairo = { version = "0.4.0", path = "../piet-cairo" } -cairo-rs = { version = "0.9.1", default_features = false} -cairo-sys-rs = { version = "0.10.0" } +cairo-rs = { version = "0.14.0", default_features = false} +cairo-sys-rs = { version = "0.14.0" } [target.'cfg(target_os="macos")'.dependencies] piet-coregraphics = { version = "0.4.0", path = "../piet-coregraphics" } diff --git a/piet-common/src/cairo_back.rs b/piet-common/src/cairo_back.rs index d4e28d5e..1ab3d45a 100644 --- a/piet-common/src/cairo_back.rs +++ b/piet-common/src/cairo_back.rs @@ -79,7 +79,7 @@ impl Device { pix_scale: f64, ) -> Result { let surface = ImageSurface::create(Format::ARgb32, width as i32, height as i32).unwrap(); - let cr = Context::new(&surface); + let cr = Context::new(&surface).unwrap(); cr.scale(pix_scale, pix_scale); let phantom = Default::default(); Ok(BitmapTarget { @@ -112,9 +112,9 @@ impl<'a> BitmapTarget<'a> { return Err(piet::Error::NotSupported); } self.surface.flush(); - let stride = self.surface.get_stride() as usize; - let width = self.surface.get_width() as usize; - let height = self.surface.get_height() as usize; + let stride = self.surface.stride() as usize; + let width = self.surface.width() as usize; + let height = self.surface.height() as usize; let size = width * height * 4; if buf.len() < size { return Err(piet::Error::InvalidInput); @@ -168,8 +168,8 @@ impl<'a> BitmapTarget<'a> { // really a mutation, so we'll keep the name. Consider using interior mutability in the future. #[allow(clippy::wrong_self_convention)] pub fn to_image_buf(&mut self, fmt: ImageFormat) -> Result { - let width = self.surface.get_width() as usize; - let height = self.surface.get_height() as usize; + let width = self.surface.width() as usize; + let height = self.surface.height() as usize; let mut buf = vec![0; width * height * 4]; self.copy_raw_pixels(fmt, &mut buf)?; Ok(ImageBuf::from_raw(buf, fmt, width, height)) @@ -178,8 +178,8 @@ impl<'a> BitmapTarget<'a> { /// Save bitmap to RGBA PNG file #[cfg(feature = "png")] pub fn save_to_file>(mut self, path: P) -> Result<(), piet::Error> { - let height = self.surface.get_height(); - let width = self.surface.get_width(); + let height = self.surface.height(); + let width = self.surface.width(); let image = self.to_image_buf(ImageFormat::RgbaPremul)?; let file = BufWriter::new(File::create(path).map_err(Into::>::into)?); let mut encoder = Encoder::new(file, width as u32, height as u32);