librsvg 2.63.2

A library to render SVG images to Cairo surfaces. GNOME uses this to render SVG icons. Outside of GNOME, other desktop environments use it for similar purposes. Wikimedia uses it for Wikipedia's SVG diagrams.
Documentation
//! Utilities for dealing with Cairo paths.
//!
//! Librsvg uses Cairo to render Bézier paths, and also depends on Cairo to
//! compute the extents of those paths.  This module holds a number of utilities
//! to convert between librsvg paths and Cairo paths.

use std::f64::consts::PI;

use crate::error::InternalRenderingError;
use crate::path_builder::{
    arc_segment, ArcParameterization, CubicBezierCurve, EllipticalArc, Path, PathBuilder,
    PathCommand,
};
use crate::rect::Rect;

pub fn compute_path_extents(path: &Path) -> Result<Option<Rect>, InternalRenderingError> {
    if path.is_empty() {
        return Ok(None);
    }

    let surface = cairo::RecordingSurface::create(cairo::Content::ColorAlpha, None)?;
    let cr = cairo::Context::new(&surface)?;

    path.to_cairo(&cr, false)?;
    let (x0, y0, x1, y1) = cr.path_extents()?;

    Ok(Some(Rect::new(x0, y0, x1, y1)))
}

impl Path {
    pub fn to_cairo(
        &self,
        cr: &cairo::Context,
        is_square_linecap: bool,
    ) -> Result<(), InternalRenderingError> {
        assert!(!self.is_empty());

        for subpath in self.iter_subpath() {
            // If a subpath is empty and the linecap is a square, then draw a square centered on
            // the origin of the subpath. See #165.
            if is_square_linecap {
                let (x, y) = subpath.origin();
                if subpath.is_zero_length() {
                    let stroke_size = 0.002;

                    cr.move_to(x - stroke_size / 2., y);
                    cr.line_to(x + stroke_size / 2., y);
                }
            }

            for cmd in subpath.iter_commands() {
                cmd.to_cairo(cr);
            }
        }

        // We check the cr's status right after feeding it a new path for a few reasons:
        //
        // * Any of the individual path commands may cause the cr to enter an error state, for
        //   example, if they come with coordinates outside of Cairo's supported range.
        //
        // * The *next* call to the cr will probably be something that actually checks the status
        //   (i.e. in cairo-rs), and we don't want to panic there.

        cr.status().map_err(|e| e.into())
    }

    /// Converts a `cairo::Path` to a librsvg `Path`.
    pub fn from_cairo(cairo_path: cairo::Path) -> Path {
        let mut builder = PathBuilder::default();

        // Cairo has the habit of appending a MoveTo to some paths, but we don't want a
        // path for empty text to generate that lone point.  So, strip out paths composed
        // only of MoveTo.

        if !cairo_path_is_only_move_tos(&cairo_path) {
            for segment in cairo_path.iter() {
                match segment {
                    cairo::PathSegment::MoveTo((x, y)) => builder.move_to(x, y),
                    cairo::PathSegment::LineTo((x, y)) => builder.line_to(x, y),
                    cairo::PathSegment::CurveTo((x2, y2), (x3, y3), (x4, y4)) => {
                        builder.curve_to(x2, y2, x3, y3, x4, y4)
                    }
                    cairo::PathSegment::ClosePath => builder.close_path(),
                }
            }
        }

        builder.into_path()
    }
}

fn cairo_path_is_only_move_tos(path: &cairo::Path) -> bool {
    path.iter()
        .all(|seg| matches!(seg, cairo::PathSegment::MoveTo((_, _))))
}

impl PathCommand {
    fn to_cairo(&self, cr: &cairo::Context) {
        match *self {
            PathCommand::MoveTo(x, y) => cr.move_to(x, y),
            PathCommand::LineTo(x, y) => cr.line_to(x, y),
            PathCommand::CurveTo(ref curve) => curve.to_cairo(cr),
            PathCommand::Arc(ref arc) => arc.to_cairo(cr),
            PathCommand::ClosePath => cr.close_path(),
        }
    }
}

impl EllipticalArc {
    fn to_cairo(&self, cr: &cairo::Context) {
        match self.center_parameterization() {
            ArcParameterization::CenterParameters {
                center,
                radii,
                theta1,
                delta_theta,
            } => {
                let n_segs = (delta_theta / (PI * 0.5 + 0.001)).abs().ceil() as u32;
                let d_theta = delta_theta / f64::from(n_segs);

                let mut theta = theta1;
                for _ in 0..n_segs {
                    arc_segment(center, radii, self.x_axis_rotation, theta, theta + d_theta)
                        .to_cairo(cr);
                    theta += d_theta;
                }
            }
            ArcParameterization::LineTo => {
                let (x2, y2) = self.to;
                cr.line_to(x2, y2);
            }
            ArcParameterization::Omit => {}
        }
    }
}

impl CubicBezierCurve {
    fn to_cairo(&self, cr: &cairo::Context) {
        let Self { pt1, pt2, to } = *self;
        cr.curve_to(pt1.0, pt1.1, pt2.0, pt2.1, to.0, to.1);
    }
}