<commit_before>use cairo;
use cairo::MatrixTrait;
use cairo::Pattern as CairoPattern;
use std::cell::RefCell;
use std::f64;
use std::rc::*;

use aspect_ratio::*;
use attributes::Attribute;
use bbox::*;
use coord_units::CoordUnits;
use drawing_ctx::{DrawingCtx, NodeStack};
use error::RenderingError;
use float_eq_cairo::ApproxEqCairo;
use handle::RsvgHandle;
use length::*;
use node::*;
use parsers::{parse, parse_and_validate};
use property_bag::PropertyBag;
use state::ComputedValues;
use viewbox::*;

coord_units!(PatternUnits, CoordUnits::ObjectBoundingBox);
coord_units!(PatternContentUnits, CoordUnits::UserSpaceOnUse);

#[derive(Clone)]
struct Pattern {
    pub units: Option<PatternUnits>,
    pub content_units: Option<PatternContentUnits>,
    // This Option<Option<ViewBox>> is a bit strange.  We want a field
    // with value None to mean, "this field isn't resolved yet".  However,
    // the vbox can very well be *not* specified in the SVG file.
    // In that case, the fully resolved pattern will have a .vbox=Some(None) value.
    pub vbox: Option<Option<ViewBox>>,
    pub preserve_aspect_ratio: Option<AspectRatio>,
    pub affine: Option<cairo::Matrix>,
    pub fallback: Option<String>,
    pub x: Option<Length>,
    pub y: Option<Length>,
    pub width: Option<Length>,
    pub height: Option<Length>,

    // Point back to our corresponding node, or to the fallback node which has children.
    // If the value is None, it means we are fully resolved and didn't find any children
    // among the fallbacks.
    pub node: Option<Weak<Node>>,
}

impl Default for Pattern {
    fn default() -> Pattern {
        // These are per the spec

        Pattern {
            units: Some(PatternUnits::default()),
            content_units: Some(PatternContentUnits::default()),
            vbox: Some(None),
            preserve_aspect_ratio: Some(AspectRatio::default()),
            affine: Some(cairo::Matrix::identity()),
            fallback: None,
            x: Some(Length::default()),
            y: Some(Length::default()),
            width: Some(Length::default()),
            height: Some(Length::default()),
            node: None,
        }
    }
}

// All of the Pattern's fields are Option<foo> values, because
// those fields can be omitted in the SVG file.  We need to resolve
// them to default values, or to fallback values that come from
// another Pattern.
//
// For the fallback case, this would need something like
//
//    if self.foo.is_none () { self.foo = fallback.foo; }
//
// And for the default case, it would be like
//    if self.foo.is_none () { self.foo = Some (default_value); }
//
// Both can be replaced by
//
//    self.foo = self.foo.take ().or (bar);
//
// So we define a macro for that.
macro_rules! fallback_to (
    ($dest:expr, $default:expr) => (
        $dest = $dest.take ().or ($default)
    );
);

impl Pattern {
    fn unresolved() -> Pattern {
        Pattern {
            units: None,
            content_units: None,
            vbox: None,
            preserve_aspect_ratio: None,
            affine: None,
            fallback: None,
            x: None,
            y: None,
            width: None,
            height: None,
            node: None,
        }
    }

    fn is_resolved(&self) -> bool {
        self.units.is_some()
            && self.content_units.is_some()
            && self.vbox.is_some()
            && self.preserve_aspect_ratio.is_some()
            && self.affine.is_some()
            && self.x.is_some()
            && self.y.is_some()
            && self.width.is_some()
            && self.height.is_some()
            && self.children_are_resolved()
    }

    fn children_are_resolved(&self) -> bool {
        if let Some(ref weak) = self.node {
            let strong_node = &weak.clone().upgrade().unwrap();
            strong_node.has_children()
        } else {
            // We are an empty pattern; there is nothing further that
            // can be resolved for children.
            true
        }
    }

    fn resolve_from_defaults(&mut self) {
        self.resolve_from_fallback(&Pattern::default());
    }

    fn resolve_from_fallback(&mut self, fallback: &Pattern) {
        fallback_to!(self.units, fallback.units);
        fallback_to!(self.content_units, fallback.content_units);
        fallback_to!(self.vbox, fallback.vbox);
        fallback_to!(self.preserve_aspect_ratio, fallback.preserve_aspect_ratio);
        fallback_to!(self.affine, fallback.affine);
        fallback_to!(self.x, fallback.x);
        fallback_to!(self.y, fallback.y);
        fallback_to!(self.width, fallback.width);
        fallback_to!(self.height, fallback.height);

        self.fallback = fallback.fallback.clone();

        if !self.children_are_resolved() {
            if fallback.node.is_some() {
                self.node = fallback.node.clone();
            } else {
                self.node = None;
            }
        }
    }
}

pub struct NodePattern {
    pattern: RefCell<Pattern>,
}

impl NodePattern {
    pub fn new() -> NodePattern {
        NodePattern {
            pattern: RefCell::new(Pattern::unresolved()),
        }
    }
}

impl NodeTrait for NodePattern {
    fn set_atts(
        &self,
        node: &RsvgNode,
        _: *const RsvgHandle,
        pbag: &PropertyBag<'_>,
    ) -> NodeResult {
        // pattern element has overflow:hidden
        // https://www.w3.org/TR/SVG/styling.html#UAStyleSheet
        node.set_overflow_hidden();

        let mut p = self.pattern.borrow_mut();

        p.node = Some(Rc::downgrade(node));

        for (_key, attr, value) in pbag.iter() {
            match attr {
                Attribute::PatternUnits => p.units = Some(parse("patternUnits", value, ())?),

                Attribute::PatternContentUnits => {
                    p.content_units = Some(parse("patternContentUnits", value, ())?)
                }

                Attribute::ViewBox => p.vbox = Some(Some(parse("viewBox", value, ())?)),

                Attribute::PreserveAspectRatio => {
                    p.preserve_aspect_ratio = Some(parse("preserveAspectRatio", value, ())?)
                }

                Attribute::PatternTransform => {
                    p.affine = Some(parse("patternTransform", value, ())?)
                }

                Attribute::XlinkHref => p.fallback = Some(value.to_owned()),

                Attribute::X => p.x = Some(parse("x", value, LengthDir::Horizontal)?),

                Attribute::Y => p.y = Some(parse("y", value, LengthDir::Vertical)?),

                Attribute::Width => {
                    p.width = Some(parse_and_validate(
                        "width",
                        value,
                        LengthDir::Horizontal,
                        Length::check_nonnegative,
                    )?)
                }

                Attribute::Height => {
                    p.height = Some(parse_and_validate(
                        "height",
                        value,
                        LengthDir::Vertical,
                        Length::check_nonnegative,
                    )?)
                }

                _ => (),
            }
        }

        Ok(())
    }
}

fn resolve_pattern(pattern: &Pattern, draw_ctx: &mut DrawingCtx<'_>) -> Pattern {
    let mut result = pattern.clone();

    let mut stack = NodeStack::new();

    while !result.is_resolved() {
        if let Some(acquired) = draw_ctx.get_acquired_node_of_type(
            result.fallback.as_ref().map(String::as_ref),
            NodeType::Pattern,
        ) {
            let node = acquired.get();

            if stack.contains(node) {
                result.resolve_from_defaults();
                break; // reference cycle; bail out
            }

            node.with_impl(|i: &NodePattern| result.resolve_from_fallback(&*i.pattern.borrow()));

            stack.push(node);
        } else {
            result.resolve_from_defaults();
        }
    }

    result
}

fn set_pattern_on_draw_context(
    pattern: &Pattern,
    values: &ComputedValues,
    draw_ctx: &mut DrawingCtx<'_>,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    assert!(pattern.is_resolved());

    if pattern.node.is_none() {
        // This means we didn't find any children among the fallbacks,
        // so there is nothing to render.
        return Ok(false);
    }

    let units = pattern.units.unwrap();
    let content_units = pattern.content_units.unwrap();
    let pattern_affine = pattern.affine.unwrap();
    let vbox = pattern.vbox.unwrap();
    let preserve_aspect_ratio = pattern.preserve_aspect_ratio.unwrap();

    let (pattern_x, pattern_y, pattern_width, pattern_height) = {
        let params = if units == PatternUnits(CoordUnits::ObjectBoundingBox) {
            draw_ctx.push_view_box(1.0, 1.0)
        } else {
            draw_ctx.get_view_params()
        };

        let pattern_x = pattern.x.unwrap().normalize(values, &params);
        let pattern_y = pattern.y.unwrap().normalize(values, &params);
        let pattern_width = pattern.width.unwrap().normalize(values, &params);
        let pattern_height = pattern.height.unwrap().normalize(values, &params);

        (pattern_x, pattern_y, pattern_width, pattern_height)
    };

    // Work out the size of the rectangle so it takes into account the object bounding box

    let bbwscale: f64;
    let bbhscale: f64;

    match units {
        PatternUnits(CoordUnits::ObjectBoundingBox) => {
            let bbrect = bbox.rect.unwrap();
            bbwscale = bbrect.width;
            bbhscale = bbrect.height;
        }

        PatternUnits(CoordUnits::UserSpaceOnUse) => {
            bbwscale = 1.0;
            bbhscale = 1.0;
        }
    }

    let cr = draw_ctx.get_cairo_context();
    let affine = cr.get_matrix();
    let taffine = cairo::Matrix::multiply(&pattern_affine, &affine);

    let mut scwscale = (taffine.xx * taffine.xx + taffine.xy * taffine.xy).sqrt();
    let mut schscale = (taffine.yx * taffine.yx + taffine.yy * taffine.yy).sqrt();

    let pw: i32 = (pattern_width * bbwscale * scwscale) as i32;
    let ph: i32 = (pattern_height * bbhscale * schscale) as i32;

    let scaled_width = pattern_width * bbwscale;
    let scaled_height = pattern_height * bbhscale;

    if scaled_width.abs() < f64::EPSILON || scaled_height.abs() < f64::EPSILON || pw < 1 || ph < 1 {
        return Ok(false);
    }

    scwscale = f64::from(pw) / scaled_width;
    schscale = f64::from(ph) / scaled_height;

    let mut affine: cairo::Matrix = cairo::Matrix::identity();

    // Create the pattern coordinate system
    match units {
        PatternUnits(CoordUnits::ObjectBoundingBox) => {
            let bbrect = bbox.rect.unwrap();
            affine.translate(
                bbrect.x + pattern_x * bbrect.width,
                bbrect.y + pattern_y * bbrect.height,
            );
        }

        PatternUnits(CoordUnits::UserSpaceOnUse) => {
            affine.translate(pattern_x, pattern_y);
        }
    }

    // Apply the pattern transform
    affine = cairo::Matrix::multiply(&affine, &pattern_affine);

    let mut caffine: cairo::Matrix;

    // Create the pattern contents coordinate system
    let _params = if let Some(vbox) = vbox {
        // If there is a vbox, use that
        let (mut x, mut y, w, h) = preserve_aspect_ratio.compute(
            vbox.0.width,
            vbox.0.height,
            0.0,
            0.0,
            pattern_width * bbwscale,
            pattern_height * bbhscale,
        );

        x -= vbox.0.x * w / vbox.0.width;
        y -= vbox.0.y * h / vbox.0.height;

        caffine = cairo::Matrix::new(w / vbox.0.width, 0.0, 0.0, h / vbox.0.height, x, y);

        draw_ctx.push_view_box(vbox.0.width, vbox.0.height)
    } else if content_units == PatternContentUnits(CoordUnits::ObjectBoundingBox) {
        // If coords are in terms of the bounding box, use them
        let bbrect = bbox.rect.unwrap();

        caffine = cairo::Matrix::identity();
        caffine.scale(bbrect.width, bbrect.height);

        draw_ctx.push_view_box(1.0, 1.0)
    } else {
        caffine = cairo::Matrix::identity();
        draw_ctx.get_view_params()
    };

    if !scwscale.approx_eq_cairo(&1.0) || !schscale.approx_eq_cairo(&1.0) {
        let mut scalematrix = cairo::Matrix::identity();
        scalematrix.scale(scwscale, schscale);
        caffine = cairo::Matrix::multiply(&caffine, &scalematrix);

        scalematrix = cairo::Matrix::identity();
        scalematrix.scale(1.0 / scwscale, 1.0 / schscale);

        affine = cairo::Matrix::multiply(&scalematrix, &affine);
    }

    // Draw to another surface

    let cr_save = draw_ctx.get_cairo_context();

    let surface = cr_save
        .get_target()
        .create_similar(cairo::Content::ColorAlpha, pw, ph);

    let cr_pattern = cairo::Context::new(&surface);

    draw_ctx.set_cairo_context(&cr_pattern);

    // Set up transformations to be determined by the contents units

    // Draw everything
    let pattern_node = pattern.node.clone().unwrap().upgrade().unwrap();
    let pattern_cascaded = pattern_node.get_cascaded_values();
    let pattern_values = pattern_cascaded.get();

    cr_pattern.set_matrix(caffine);

    let res = draw_ctx.with_discrete_layer(&pattern_node, pattern_values, false, &mut |dc| {
        pattern_node.draw_children(&pattern_cascaded, dc, false)
    });

    // Return to the original coordinate system and rendering context

    draw_ctx.set_cairo_context(&cr_save);

    // Set the final surface as a Cairo pattern into the Cairo context

    let surface_pattern = cairo::SurfacePattern::create(&surface);
    surface_pattern.set_extend(cairo::Extend::Repeat);

    let mut matrix = affine;
    matrix.invert();

    surface_pattern.set_matrix(matrix);
    surface_pattern.set_filter(cairo::Filter::Best);

    cr_save.set_source(&surface_pattern);

    res.and_then(|_| Ok(true))
}

fn resolve_fallbacks_and_set_pattern(
    pattern: &Pattern,
    values: &ComputedValues,
    draw_ctx: &mut DrawingCtx<'_>,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    let resolved = resolve_pattern(pattern, draw_ctx);

    set_pattern_on_draw_context(&resolved, values, draw_ctx, bbox)
}

pub fn pattern_resolve_fallbacks_and_set_pattern(
    node: &RsvgNode,
    draw_ctx: &mut DrawingCtx<'_>,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    assert!(node.get_type() == NodeType::Pattern);

    node.with_impl(|node_pattern: &NodePattern| {
        let pattern = &*node_pattern.pattern.borrow();
        let cascaded = node.get_cascaded_values();
        let values = cascaded.get();

        resolve_fallbacks_and_set_pattern(pattern, values, draw_ctx, bbox)
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pattern_resolved_from_defaults_is_really_resolved() {
        let mut pat = Pattern::unresolved();

        pat.resolve_from_defaults();
        assert!(pat.is_resolved());
    }
}
<commit_msg>pattern.rs: Log when we find a circular reference<commit_after>use cairo;
use cairo::MatrixTrait;
use cairo::Pattern as CairoPattern;
use std::cell::RefCell;
use std::f64;
use std::rc::*;

use aspect_ratio::*;
use attributes::Attribute;
use bbox::*;
use coord_units::CoordUnits;
use drawing_ctx::{DrawingCtx, NodeStack};
use error::RenderingError;
use float_eq_cairo::ApproxEqCairo;
use handle::RsvgHandle;
use length::*;
use node::*;
use parsers::{parse, parse_and_validate};
use property_bag::PropertyBag;
use state::ComputedValues;
use viewbox::*;

coord_units!(PatternUnits, CoordUnits::ObjectBoundingBox);
coord_units!(PatternContentUnits, CoordUnits::UserSpaceOnUse);

#[derive(Clone)]
struct Pattern {
    pub units: Option<PatternUnits>,
    pub content_units: Option<PatternContentUnits>,
    // This Option<Option<ViewBox>> is a bit strange.  We want a field
    // with value None to mean, "this field isn't resolved yet".  However,
    // the vbox can very well be *not* specified in the SVG file.
    // In that case, the fully resolved pattern will have a .vbox=Some(None) value.
    pub vbox: Option<Option<ViewBox>>,
    pub preserve_aspect_ratio: Option<AspectRatio>,
    pub affine: Option<cairo::Matrix>,
    pub fallback: Option<String>,
    pub x: Option<Length>,
    pub y: Option<Length>,
    pub width: Option<Length>,
    pub height: Option<Length>,

    // Point back to our corresponding node, or to the fallback node which has children.
    // If the value is None, it means we are fully resolved and didn't find any children
    // among the fallbacks.
    pub node: Option<Weak<Node>>,
}

impl Default for Pattern {
    fn default() -> Pattern {
        // These are per the spec

        Pattern {
            units: Some(PatternUnits::default()),
            content_units: Some(PatternContentUnits::default()),
            vbox: Some(None),
            preserve_aspect_ratio: Some(AspectRatio::default()),
            affine: Some(cairo::Matrix::identity()),
            fallback: None,
            x: Some(Length::default()),
            y: Some(Length::default()),
            width: Some(Length::default()),
            height: Some(Length::default()),
            node: None,
        }
    }
}

// All of the Pattern's fields are Option<foo> values, because
// those fields can be omitted in the SVG file.  We need to resolve
// them to default values, or to fallback values that come from
// another Pattern.
//
// For the fallback case, this would need something like
//
//    if self.foo.is_none () { self.foo = fallback.foo; }
//
// And for the default case, it would be like
//    if self.foo.is_none () { self.foo = Some (default_value); }
//
// Both can be replaced by
//
//    self.foo = self.foo.take ().or (bar);
//
// So we define a macro for that.
macro_rules! fallback_to (
    ($dest:expr, $default:expr) => (
        $dest = $dest.take ().or ($default)
    );
);

impl Pattern {
    fn unresolved() -> Pattern {
        Pattern {
            units: None,
            content_units: None,
            vbox: None,
            preserve_aspect_ratio: None,
            affine: None,
            fallback: None,
            x: None,
            y: None,
            width: None,
            height: None,
            node: None,
        }
    }

    fn is_resolved(&self) -> bool {
        self.units.is_some()
            && self.content_units.is_some()
            && self.vbox.is_some()
            && self.preserve_aspect_ratio.is_some()
            && self.affine.is_some()
            && self.x.is_some()
            && self.y.is_some()
            && self.width.is_some()
            && self.height.is_some()
            && self.children_are_resolved()
    }

    fn children_are_resolved(&self) -> bool {
        if let Some(ref weak) = self.node {
            let strong_node = &weak.clone().upgrade().unwrap();
            strong_node.has_children()
        } else {
            // We are an empty pattern; there is nothing further that
            // can be resolved for children.
            true
        }
    }

    fn resolve_from_defaults(&mut self) {
        self.resolve_from_fallback(&Pattern::default());
    }

    fn resolve_from_fallback(&mut self, fallback: &Pattern) {
        fallback_to!(self.units, fallback.units);
        fallback_to!(self.content_units, fallback.content_units);
        fallback_to!(self.vbox, fallback.vbox);
        fallback_to!(self.preserve_aspect_ratio, fallback.preserve_aspect_ratio);
        fallback_to!(self.affine, fallback.affine);
        fallback_to!(self.x, fallback.x);
        fallback_to!(self.y, fallback.y);
        fallback_to!(self.width, fallback.width);
        fallback_to!(self.height, fallback.height);

        self.fallback = fallback.fallback.clone();

        if !self.children_are_resolved() {
            if fallback.node.is_some() {
                self.node = fallback.node.clone();
            } else {
                self.node = None;
            }
        }
    }
}

pub struct NodePattern {
    pattern: RefCell<Pattern>,
}

impl NodePattern {
    pub fn new() -> NodePattern {
        NodePattern {
            pattern: RefCell::new(Pattern::unresolved()),
        }
    }
}

impl NodeTrait for NodePattern {
    fn set_atts(
        &self,
        node: &RsvgNode,
        _: *const RsvgHandle,
        pbag: &PropertyBag<'_>,
    ) -> NodeResult {
        // pattern element has overflow:hidden
        // https://www.w3.org/TR/SVG/styling.html#UAStyleSheet
        node.set_overflow_hidden();

        let mut p = self.pattern.borrow_mut();

        p.node = Some(Rc::downgrade(node));

        for (_key, attr, value) in pbag.iter() {
            match attr {
                Attribute::PatternUnits => p.units = Some(parse("patternUnits", value, ())?),

                Attribute::PatternContentUnits => {
                    p.content_units = Some(parse("patternContentUnits", value, ())?)
                }

                Attribute::ViewBox => p.vbox = Some(Some(parse("viewBox", value, ())?)),

                Attribute::PreserveAspectRatio => {
                    p.preserve_aspect_ratio = Some(parse("preserveAspectRatio", value, ())?)
                }

                Attribute::PatternTransform => {
                    p.affine = Some(parse("patternTransform", value, ())?)
                }

                Attribute::XlinkHref => p.fallback = Some(value.to_owned()),

                Attribute::X => p.x = Some(parse("x", value, LengthDir::Horizontal)?),

                Attribute::Y => p.y = Some(parse("y", value, LengthDir::Vertical)?),

                Attribute::Width => {
                    p.width = Some(parse_and_validate(
                        "width",
                        value,
                        LengthDir::Horizontal,
                        Length::check_nonnegative,
                    )?)
                }

                Attribute::Height => {
                    p.height = Some(parse_and_validate(
                        "height",
                        value,
                        LengthDir::Vertical,
                        Length::check_nonnegative,
                    )?)
                }

                _ => (),
            }
        }

        Ok(())
    }
}

fn resolve_pattern(pattern: &Pattern, draw_ctx: &mut DrawingCtx<'_>) -> Pattern {
    let mut result = pattern.clone();

    let mut stack = NodeStack::new();

    while !result.is_resolved() {
        if let Some(acquired) = draw_ctx.get_acquired_node_of_type(
            result.fallback.as_ref().map(String::as_ref),
            NodeType::Pattern,
        ) {
            let node = acquired.get();

            if stack.contains(node) {
                // FIXME: return a Result here with RenderingError::CircularReference
                // FIXME: print the pattern's name
                rsvg_log!("circular reference in pattern");
                result.resolve_from_defaults();
                break;
            }

            node.with_impl(|i: &NodePattern| result.resolve_from_fallback(&*i.pattern.borrow()));

            stack.push(node);
        } else {
            result.resolve_from_defaults();
        }
    }

    result
}

fn set_pattern_on_draw_context(
    pattern: &Pattern,
    values: &ComputedValues,
    draw_ctx: &mut DrawingCtx<'_>,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    assert!(pattern.is_resolved());

    if pattern.node.is_none() {
        // This means we didn't find any children among the fallbacks,
        // so there is nothing to render.
        return Ok(false);
    }

    let units = pattern.units.unwrap();
    let content_units = pattern.content_units.unwrap();
    let pattern_affine = pattern.affine.unwrap();
    let vbox = pattern.vbox.unwrap();
    let preserve_aspect_ratio = pattern.preserve_aspect_ratio.unwrap();

    let (pattern_x, pattern_y, pattern_width, pattern_height) = {
        let params = if units == PatternUnits(CoordUnits::ObjectBoundingBox) {
            draw_ctx.push_view_box(1.0, 1.0)
        } else {
            draw_ctx.get_view_params()
        };

        let pattern_x = pattern.x.unwrap().normalize(values, &params);
        let pattern_y = pattern.y.unwrap().normalize(values, &params);
        let pattern_width = pattern.width.unwrap().normalize(values, &params);
        let pattern_height = pattern.height.unwrap().normalize(values, &params);

        (pattern_x, pattern_y, pattern_width, pattern_height)
    };

    // Work out the size of the rectangle so it takes into account the object bounding box

    let bbwscale: f64;
    let bbhscale: f64;

    match units {
        PatternUnits(CoordUnits::ObjectBoundingBox) => {
            let bbrect = bbox.rect.unwrap();
            bbwscale = bbrect.width;
            bbhscale = bbrect.height;
        }

        PatternUnits(CoordUnits::UserSpaceOnUse) => {
            bbwscale = 1.0;
            bbhscale = 1.0;
        }
    }

    let cr = draw_ctx.get_cairo_context();
    let affine = cr.get_matrix();
    let taffine = cairo::Matrix::multiply(&pattern_affine, &affine);

    let mut scwscale = (taffine.xx * taffine.xx + taffine.xy * taffine.xy).sqrt();
    let mut schscale = (taffine.yx * taffine.yx + taffine.yy * taffine.yy).sqrt();

    let pw: i32 = (pattern_width * bbwscale * scwscale) as i32;
    let ph: i32 = (pattern_height * bbhscale * schscale) as i32;

    let scaled_width = pattern_width * bbwscale;
    let scaled_height = pattern_height * bbhscale;

    if scaled_width.abs() < f64::EPSILON || scaled_height.abs() < f64::EPSILON || pw < 1 || ph < 1 {
        return Ok(false);
    }

    scwscale = f64::from(pw) / scaled_width;
    schscale = f64::from(ph) / scaled_height;

    let mut affine: cairo::Matrix = cairo::Matrix::identity();

    // Create the pattern coordinate system
    match units {
        PatternUnits(CoordUnits::ObjectBoundingBox) => {
            let bbrect = bbox.rect.unwrap();
            affine.translate(
                bbrect.x + pattern_x * bbrect.width,
                bbrect.y + pattern_y * bbrect.height,
            );
        }

        PatternUnits(CoordUnits::UserSpaceOnUse) => {
            affine.translate(pattern_x, pattern_y);
        }
    }

    // Apply the pattern transform
    affine = cairo::Matrix::multiply(&affine, &pattern_affine);

    let mut caffine: cairo::Matrix;

    // Create the pattern contents coordinate system
    let _params = if let Some(vbox) = vbox {
        // If there is a vbox, use that
        let (mut x, mut y, w, h) = preserve_aspect_ratio.compute(
            vbox.0.width,
            vbox.0.height,
            0.0,
            0.0,
            pattern_width * bbwscale,
            pattern_height * bbhscale,
        );

        x -= vbox.0.x * w / vbox.0.width;
        y -= vbox.0.y * h / vbox.0.height;

        caffine = cairo::Matrix::new(w / vbox.0.width, 0.0, 0.0, h / vbox.0.height, x, y);

        draw_ctx.push_view_box(vbox.0.width, vbox.0.height)
    } else if content_units == PatternContentUnits(CoordUnits::ObjectBoundingBox) {
        // If coords are in terms of the bounding box, use them
        let bbrect = bbox.rect.unwrap();

        caffine = cairo::Matrix::identity();
        caffine.scale(bbrect.width, bbrect.height);

        draw_ctx.push_view_box(1.0, 1.0)
    } else {
        caffine = cairo::Matrix::identity();
        draw_ctx.get_view_params()
    };

    if !scwscale.approx_eq_cairo(&1.0) || !schscale.approx_eq_cairo(&1.0) {
        let mut scalematrix = cairo::Matrix::identity();
        scalematrix.scale(scwscale, schscale);
        caffine = cairo::Matrix::multiply(&caffine, &scalematrix);

        scalematrix = cairo::Matrix::identity();
        scalematrix.scale(1.0 / scwscale, 1.0 / schscale);

        affine = cairo::Matrix::multiply(&scalematrix, &affine);
    }

    // Draw to another surface

    let cr_save = draw_ctx.get_cairo_context();

    let surface = cr_save
        .get_target()
        .create_similar(cairo::Content::ColorAlpha, pw, ph);

    let cr_pattern = cairo::Context::new(&surface);

    draw_ctx.set_cairo_context(&cr_pattern);

    // Set up transformations to be determined by the contents units

    // Draw everything
    let pattern_node = pattern.node.clone().unwrap().upgrade().unwrap();
    let pattern_cascaded = pattern_node.get_cascaded_values();
    let pattern_values = pattern_cascaded.get();

    cr_pattern.set_matrix(caffine);

    let res = draw_ctx.with_discrete_layer(&pattern_node, pattern_values, false, &mut |dc| {
        pattern_node.draw_children(&pattern_cascaded, dc, false)
    });

    // Return to the original coordinate system and rendering context

    draw_ctx.set_cairo_context(&cr_save);

    // Set the final surface as a Cairo pattern into the Cairo context

    let surface_pattern = cairo::SurfacePattern::create(&surface);
    surface_pattern.set_extend(cairo::Extend::Repeat);

    let mut matrix = affine;
    matrix.invert();

    surface_pattern.set_matrix(matrix);
    surface_pattern.set_filter(cairo::Filter::Best);

    cr_save.set_source(&surface_pattern);

    res.and_then(|_| Ok(true))
}

fn resolve_fallbacks_and_set_pattern(
    pattern: &Pattern,
    values: &ComputedValues,
    draw_ctx: &mut DrawingCtx<'_>,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    let resolved = resolve_pattern(pattern, draw_ctx);

    set_pattern_on_draw_context(&resolved, values, draw_ctx, bbox)
}

pub fn pattern_resolve_fallbacks_and_set_pattern(
    node: &RsvgNode,
    draw_ctx: &mut DrawingCtx<'_>,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    assert!(node.get_type() == NodeType::Pattern);

    node.with_impl(|node_pattern: &NodePattern| {
        let pattern = &*node_pattern.pattern.borrow();
        let cascaded = node.get_cascaded_values();
        let values = cascaded.get();

        resolve_fallbacks_and_set_pattern(pattern, values, draw_ctx, bbox)
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pattern_resolved_from_defaults_is_really_resolved() {
        let mut pat = Pattern::unresolved();

        pat.resolve_from_defaults();
        assert!(pat.is_resolved());
    }
}
<|endoftext|>


<commit_before>use cairo;
use cairo::MatrixTrait;
use cairo::PatternTrait;
use std::cell::RefCell;
use std::f64;
use std::rc::*;

use aspect_ratio::*;
use attributes::Attribute;
use bbox::*;
use coord_units::CoordUnits;
use defs::Fragment;
use drawing_ctx::{DrawingCtx, NodeStack};
use error::{AttributeResultExt, RenderingError};
use float_eq_cairo::ApproxEqCairo;
use handle::RsvgHandle;
use length::*;
use node::*;
use paint_server::PaintSource;
use parsers::ParseValue;
use property_bag::PropertyBag;
use state::ComputedValues;
use unit_interval::UnitInterval;
use viewbox::*;

coord_units!(PatternUnits, CoordUnits::ObjectBoundingBox);
coord_units!(PatternContentUnits, CoordUnits::UserSpaceOnUse);

#[derive(Clone)]
pub struct Pattern {
    pub units: Option<PatternUnits>,
    pub content_units: Option<PatternContentUnits>,
    // This Option<Option<ViewBox>> is a bit strange.  We want a field
    // with value None to mean, "this field isn't resolved yet".  However,
    // the vbox can very well be *not* specified in the SVG file.
    // In that case, the fully resolved pattern will have a .vbox=Some(None) value.
    pub vbox: Option<Option<ViewBox>>,
    pub preserve_aspect_ratio: Option<AspectRatio>,
    pub affine: Option<cairo::Matrix>,
    pub fallback: Option<Fragment>,
    pub x: Option<Length>,
    pub y: Option<Length>,
    pub width: Option<Length>,
    pub height: Option<Length>,

    // Point back to our corresponding node, or to the fallback node which has children.
    // If the value is None, it means we are fully resolved and didn't find any children
    // among the fallbacks.
    pub node: Option<Weak<Node>>,
}

impl Default for Pattern {
    fn default() -> Pattern {
        // These are per the spec

        Pattern {
            units: Some(PatternUnits::default()),
            content_units: Some(PatternContentUnits::default()),
            vbox: Some(None),
            preserve_aspect_ratio: Some(AspectRatio::default()),
            affine: Some(cairo::Matrix::identity()),
            fallback: None,
            x: Some(Length::default()),
            y: Some(Length::default()),
            width: Some(Length::default()),
            height: Some(Length::default()),
            node: None,
        }
    }
}

// All of the Pattern's fields are Option<foo> values, because
// those fields can be omitted in the SVG file.  We need to resolve
// them to default values, or to fallback values that come from
// another Pattern.
//
// For the fallback case, this would need something like
//
//    if self.foo.is_none () { self.foo = fallback.foo; }
//
// And for the default case, it would be like
//    if self.foo.is_none () { self.foo = Some (default_value); }
//
// Both can be replaced by
//
//    self.foo = self.foo.take ().or (bar);
//
// So we define a macro for that.
macro_rules! fallback_to (
    ($dest:expr, $default:expr) => (
        $dest = $dest.take ().or ($default)
    );
);

impl Pattern {
    fn unresolved() -> Pattern {
        Pattern {
            units: None,
            content_units: None,
            vbox: None,
            preserve_aspect_ratio: None,
            affine: None,
            fallback: None,
            x: None,
            y: None,
            width: None,
            height: None,
            node: None,
        }
    }

    fn is_resolved(&self) -> bool {
        self.units.is_some()
            && self.content_units.is_some()
            && self.vbox.is_some()
            && self.preserve_aspect_ratio.is_some()
            && self.affine.is_some()
            && self.x.is_some()
            && self.y.is_some()
            && self.width.is_some()
            && self.height.is_some()
            && self.children_are_resolved()
    }

    fn children_are_resolved(&self) -> bool {
        if let Some(ref weak) = self.node {
            let strong_node = &weak.clone().upgrade().unwrap();
            strong_node.has_children()
        } else {
            // We are an empty pattern; there is nothing further that
            // can be resolved for children.
            true
        }
    }

    fn resolve_from_defaults(&mut self) {
        self.resolve_from_fallback(&Pattern::default());
    }

    fn resolve_from_fallback(&mut self, fallback: &Pattern) {
        fallback_to!(self.units, fallback.units);
        fallback_to!(self.content_units, fallback.content_units);
        fallback_to!(self.vbox, fallback.vbox);
        fallback_to!(self.preserve_aspect_ratio, fallback.preserve_aspect_ratio);
        fallback_to!(self.affine, fallback.affine);
        fallback_to!(self.x, fallback.x);
        fallback_to!(self.y, fallback.y);
        fallback_to!(self.width, fallback.width);
        fallback_to!(self.height, fallback.height);

        self.fallback = fallback.fallback.clone();

        if !self.children_are_resolved() {
            if fallback.node.is_some() {
                self.node = fallback.node.clone();
            } else {
                self.node = None;
            }
        }
    }
}

pub struct NodePattern {
    pattern: RefCell<Pattern>,
}

impl NodePattern {
    pub fn new() -> NodePattern {
        NodePattern {
            pattern: RefCell::new(Pattern::unresolved()),
        }
    }
}

impl NodeTrait for NodePattern {
    fn set_atts(
        &self,
        node: &RsvgNode,
        _: *const RsvgHandle,
        pbag: &PropertyBag<'_>,
    ) -> NodeResult {
        // pattern element has overflow:hidden
        // https://www.w3.org/TR/SVG/styling.html#UAStyleSheet
        node.set_overflow_hidden();

        let mut p = self.pattern.borrow_mut();

        p.node = Some(Rc::downgrade(node));

        for (attr, value) in pbag.iter() {
            match attr {
                Attribute::PatternUnits => p.units = Some(attr.parse(value, ())?),

                Attribute::PatternContentUnits => p.content_units = Some(attr.parse(value, ())?),

                Attribute::ViewBox => p.vbox = Some(Some(attr.parse(value, ())?)),

                Attribute::PreserveAspectRatio => {
                    p.preserve_aspect_ratio = Some(attr.parse(value, ())?)
                }

                Attribute::PatternTransform => p.affine = Some(attr.parse(value, ())?),

                Attribute::XlinkHref => {
                    p.fallback = Some(Fragment::parse(value).attribute(Attribute::XlinkHref)?);
                }

                Attribute::X => p.x = Some(attr.parse(value, LengthDir::Horizontal)?),

                Attribute::Y => p.y = Some(attr.parse(value, LengthDir::Vertical)?),

                Attribute::Width => {
                    p.width = Some(attr.parse_and_validate(
                        value,
                        LengthDir::Horizontal,
                        Length::check_nonnegative,
                    )?)
                }

                Attribute::Height => {
                    p.height = Some(attr.parse_and_validate(
                        value,
                        LengthDir::Vertical,
                        Length::check_nonnegative,
                    )?)
                }

                _ => (),
            }
        }

        Ok(())
    }
}

impl PaintSource for NodePattern {
    type Source = Pattern;

    fn resolve(
        &self,
        node: &RsvgNode,
        draw_ctx: &mut DrawingCtx,
        _bbox: &BoundingBox,
    ) -> Result<Option<Self::Source>, RenderingError> {
        let node_pattern = node.get_impl::<NodePattern>().unwrap();
        let pattern = &*node_pattern.pattern.borrow();
        let mut result = pattern.clone();
        let mut stack = NodeStack::new();

        while !result.is_resolved() {
            if let Some(acquired) =
                draw_ctx.get_acquired_node_of_type(result.fallback.as_ref(), NodeType::Pattern)
            {
                let node = acquired.get();

                if stack.contains(node) {
                    // FIXME: print the pattern's name
                    rsvg_log!("circular reference in pattern");
                    return Err(RenderingError::CircularReference);
                }

                node.with_impl(|i: &NodePattern| {
                    result.resolve_from_fallback(&*i.pattern.borrow())
                });

                stack.push(node);
            } else {
                result.resolve_from_defaults();
            }
        }

        Ok(Some(result))
    }

    fn set_pattern_on_draw_context(
        &self,
        pattern: &Self::Source,
        values: &ComputedValues,
        draw_ctx: &mut DrawingCtx,
        _opacity: &UnitInterval,
        bbox: &BoundingBox,
    ) -> Result<bool, RenderingError> {
        assert!(pattern.is_resolved());

        if pattern.node.is_none() {
            // This means we didn't find any children among the fallbacks,
            // so there is nothing to render.
            return Ok(false);
        }

        let units = pattern.units.unwrap();
        let content_units = pattern.content_units.unwrap();
        let pattern_affine = pattern.affine.unwrap();
        let vbox = pattern.vbox.unwrap();
        let preserve_aspect_ratio = pattern.preserve_aspect_ratio.unwrap();

        let (pattern_x, pattern_y, pattern_width, pattern_height) = {
            let params = if units == PatternUnits(CoordUnits::ObjectBoundingBox) {
                draw_ctx.push_view_box(1.0, 1.0)
            } else {
                draw_ctx.get_view_params()
            };

            let pattern_x = pattern.x.unwrap().normalize(values, &params);
            let pattern_y = pattern.y.unwrap().normalize(values, &params);
            let pattern_width = pattern.width.unwrap().normalize(values, &params);
            let pattern_height = pattern.height.unwrap().normalize(values, &params);

            (pattern_x, pattern_y, pattern_width, pattern_height)
        };

        // Work out the size of the rectangle so it takes into account the object bounding box

        let bbwscale: f64;
        let bbhscale: f64;

        match units {
            PatternUnits(CoordUnits::ObjectBoundingBox) => {
                let bbrect = bbox.rect.unwrap();
                bbwscale = bbrect.width;
                bbhscale = bbrect.height;
            }

            PatternUnits(CoordUnits::UserSpaceOnUse) => {
                bbwscale = 1.0;
                bbhscale = 1.0;
            }
        }

        let cr = draw_ctx.get_cairo_context();
        let affine = cr.get_matrix();
        let taffine = cairo::Matrix::multiply(&pattern_affine, &affine);

        let mut scwscale = (taffine.xx * taffine.xx + taffine.xy * taffine.xy).sqrt();
        let mut schscale = (taffine.yx * taffine.yx + taffine.yy * taffine.yy).sqrt();

        let pw: i32 = (pattern_width * bbwscale * scwscale) as i32;
        let ph: i32 = (pattern_height * bbhscale * schscale) as i32;

        let scaled_width = pattern_width * bbwscale;
        let scaled_height = pattern_height * bbhscale;

        if scaled_width.abs() < f64::EPSILON
            || scaled_height.abs() < f64::EPSILON
            || pw < 1
            || ph < 1
        {
            return Ok(false);
        }

        scwscale = f64::from(pw) / scaled_width;
        schscale = f64::from(ph) / scaled_height;

        let mut affine: cairo::Matrix = cairo::Matrix::identity();

        // Create the pattern coordinate system
        match units {
            PatternUnits(CoordUnits::ObjectBoundingBox) => {
                let bbrect = bbox.rect.unwrap();
                affine.translate(
                    bbrect.x + pattern_x * bbrect.width,
                    bbrect.y + pattern_y * bbrect.height,
                );
            }

            PatternUnits(CoordUnits::UserSpaceOnUse) => {
                affine.translate(pattern_x, pattern_y);
            }
        }

        // Apply the pattern transform
        affine = cairo::Matrix::multiply(&affine, &pattern_affine);

        let mut caffine: cairo::Matrix;

        // Create the pattern contents coordinate system
        let _params = if let Some(vbox) = vbox {
            // If there is a vbox, use that
            let (mut x, mut y, w, h) = preserve_aspect_ratio.compute(
                vbox.0.width,
                vbox.0.height,
                0.0,
                0.0,
                pattern_width * bbwscale,
                pattern_height * bbhscale,
            );

            x -= vbox.0.x * w / vbox.0.width;
            y -= vbox.0.y * h / vbox.0.height;

            caffine = cairo::Matrix::new(w / vbox.0.width, 0.0, 0.0, h / vbox.0.height, x, y);

            draw_ctx.push_view_box(vbox.0.width, vbox.0.height)
        } else if content_units == PatternContentUnits(CoordUnits::ObjectBoundingBox) {
            // If coords are in terms of the bounding box, use them
            let bbrect = bbox.rect.unwrap();

            caffine = cairo::Matrix::identity();
            caffine.scale(bbrect.width, bbrect.height);

            draw_ctx.push_view_box(1.0, 1.0)
        } else {
            caffine = cairo::Matrix::identity();
            draw_ctx.get_view_params()
        };

        if !scwscale.approx_eq_cairo(&1.0) || !schscale.approx_eq_cairo(&1.0) {
            let mut scalematrix = cairo::Matrix::identity();
            scalematrix.scale(scwscale, schscale);
            caffine = cairo::Matrix::multiply(&caffine, &scalematrix);

            scalematrix = cairo::Matrix::identity();
            scalematrix.scale(1.0 / scwscale, 1.0 / schscale);

            affine = cairo::Matrix::multiply(&scalematrix, &affine);
        }

        // Draw to another surface

        let cr_save = draw_ctx.get_cairo_context();

        let surface = cr_save
            .get_target()
            .create_similar(cairo::Content::ColorAlpha, pw, ph);

        let cr_pattern = cairo::Context::new(&surface);

        draw_ctx.set_cairo_context(&cr_pattern);

        // Set up transformations to be determined by the contents units

        // Draw everything
        let pattern_node = pattern.node.clone().unwrap().upgrade().unwrap();
        let pattern_cascaded = pattern_node.get_cascaded_values();
        let pattern_values = pattern_cascaded.get();

        cr_pattern.set_matrix(caffine);

        let res = draw_ctx.with_discrete_layer(&pattern_node, pattern_values, false, &mut |dc| {
            pattern_node.draw_children(&pattern_cascaded, dc, false)
        });

        // Return to the original coordinate system and rendering context

        draw_ctx.set_cairo_context(&cr_save);

        // Set the final surface as a Cairo pattern into the Cairo context

        let surface_pattern = cairo::SurfacePattern::create(&surface);
        surface_pattern.set_extend(cairo::Extend::Repeat);

        let mut matrix = affine;
        matrix.invert();

        surface_pattern.set_matrix(matrix);
        surface_pattern.set_filter(cairo::Filter::Best);

        cr_save.set_source(&cairo::Pattern::SurfacePattern(surface_pattern));

        res.and_then(|_| Ok(true))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pattern_resolved_from_defaults_is_really_resolved() {
        let mut pat = Pattern::unresolved();

        pat.resolve_from_defaults();
        assert!(pat.is_resolved());
    }
}
<commit_msg>Log the element name when a pattern has a circular reference<commit_after>use cairo;
use cairo::MatrixTrait;
use cairo::PatternTrait;
use std::cell::RefCell;
use std::f64;
use std::rc::*;

use aspect_ratio::*;
use attributes::Attribute;
use bbox::*;
use coord_units::CoordUnits;
use defs::Fragment;
use drawing_ctx::{DrawingCtx, NodeStack};
use error::{AttributeResultExt, RenderingError};
use float_eq_cairo::ApproxEqCairo;
use handle::RsvgHandle;
use length::*;
use node::*;
use paint_server::PaintSource;
use parsers::ParseValue;
use property_bag::PropertyBag;
use state::ComputedValues;
use unit_interval::UnitInterval;
use viewbox::*;

coord_units!(PatternUnits, CoordUnits::ObjectBoundingBox);
coord_units!(PatternContentUnits, CoordUnits::UserSpaceOnUse);

#[derive(Clone)]
pub struct Pattern {
    pub units: Option<PatternUnits>,
    pub content_units: Option<PatternContentUnits>,
    // This Option<Option<ViewBox>> is a bit strange.  We want a field
    // with value None to mean, "this field isn't resolved yet".  However,
    // the vbox can very well be *not* specified in the SVG file.
    // In that case, the fully resolved pattern will have a .vbox=Some(None) value.
    pub vbox: Option<Option<ViewBox>>,
    pub preserve_aspect_ratio: Option<AspectRatio>,
    pub affine: Option<cairo::Matrix>,
    pub fallback: Option<Fragment>,
    pub x: Option<Length>,
    pub y: Option<Length>,
    pub width: Option<Length>,
    pub height: Option<Length>,

    // Point back to our corresponding node, or to the fallback node which has children.
    // If the value is None, it means we are fully resolved and didn't find any children
    // among the fallbacks.
    pub node: Option<Weak<Node>>,
}

impl Default for Pattern {
    fn default() -> Pattern {
        // These are per the spec

        Pattern {
            units: Some(PatternUnits::default()),
            content_units: Some(PatternContentUnits::default()),
            vbox: Some(None),
            preserve_aspect_ratio: Some(AspectRatio::default()),
            affine: Some(cairo::Matrix::identity()),
            fallback: None,
            x: Some(Length::default()),
            y: Some(Length::default()),
            width: Some(Length::default()),
            height: Some(Length::default()),
            node: None,
        }
    }
}

// All of the Pattern's fields are Option<foo> values, because
// those fields can be omitted in the SVG file.  We need to resolve
// them to default values, or to fallback values that come from
// another Pattern.
//
// For the fallback case, this would need something like
//
//    if self.foo.is_none () { self.foo = fallback.foo; }
//
// And for the default case, it would be like
//    if self.foo.is_none () { self.foo = Some (default_value); }
//
// Both can be replaced by
//
//    self.foo = self.foo.take ().or (bar);
//
// So we define a macro for that.
macro_rules! fallback_to (
    ($dest:expr, $default:expr) => (
        $dest = $dest.take ().or ($default)
    );
);

impl Pattern {
    fn unresolved() -> Pattern {
        Pattern {
            units: None,
            content_units: None,
            vbox: None,
            preserve_aspect_ratio: None,
            affine: None,
            fallback: None,
            x: None,
            y: None,
            width: None,
            height: None,
            node: None,
        }
    }

    fn is_resolved(&self) -> bool {
        self.units.is_some()
            && self.content_units.is_some()
            && self.vbox.is_some()
            && self.preserve_aspect_ratio.is_some()
            && self.affine.is_some()
            && self.x.is_some()
            && self.y.is_some()
            && self.width.is_some()
            && self.height.is_some()
            && self.children_are_resolved()
    }

    fn children_are_resolved(&self) -> bool {
        if let Some(ref weak) = self.node {
            let strong_node = &weak.clone().upgrade().unwrap();
            strong_node.has_children()
        } else {
            // We are an empty pattern; there is nothing further that
            // can be resolved for children.
            true
        }
    }

    fn resolve_from_defaults(&mut self) {
        self.resolve_from_fallback(&Pattern::default());
    }

    fn resolve_from_fallback(&mut self, fallback: &Pattern) {
        fallback_to!(self.units, fallback.units);
        fallback_to!(self.content_units, fallback.content_units);
        fallback_to!(self.vbox, fallback.vbox);
        fallback_to!(self.preserve_aspect_ratio, fallback.preserve_aspect_ratio);
        fallback_to!(self.affine, fallback.affine);
        fallback_to!(self.x, fallback.x);
        fallback_to!(self.y, fallback.y);
        fallback_to!(self.width, fallback.width);
        fallback_to!(self.height, fallback.height);

        self.fallback = fallback.fallback.clone();

        if !self.children_are_resolved() {
            if fallback.node.is_some() {
                self.node = fallback.node.clone();
            } else {
                self.node = None;
            }
        }
    }
}

pub struct NodePattern {
    pattern: RefCell<Pattern>,
}

impl NodePattern {
    pub fn new() -> NodePattern {
        NodePattern {
            pattern: RefCell::new(Pattern::unresolved()),
        }
    }
}

impl NodeTrait for NodePattern {
    fn set_atts(
        &self,
        node: &RsvgNode,
        _: *const RsvgHandle,
        pbag: &PropertyBag<'_>,
    ) -> NodeResult {
        // pattern element has overflow:hidden
        // https://www.w3.org/TR/SVG/styling.html#UAStyleSheet
        node.set_overflow_hidden();

        let mut p = self.pattern.borrow_mut();

        p.node = Some(Rc::downgrade(node));

        for (attr, value) in pbag.iter() {
            match attr {
                Attribute::PatternUnits => p.units = Some(attr.parse(value, ())?),

                Attribute::PatternContentUnits => p.content_units = Some(attr.parse(value, ())?),

                Attribute::ViewBox => p.vbox = Some(Some(attr.parse(value, ())?)),

                Attribute::PreserveAspectRatio => {
                    p.preserve_aspect_ratio = Some(attr.parse(value, ())?)
                }

                Attribute::PatternTransform => p.affine = Some(attr.parse(value, ())?),

                Attribute::XlinkHref => {
                    p.fallback = Some(Fragment::parse(value).attribute(Attribute::XlinkHref)?);
                }

                Attribute::X => p.x = Some(attr.parse(value, LengthDir::Horizontal)?),

                Attribute::Y => p.y = Some(attr.parse(value, LengthDir::Vertical)?),

                Attribute::Width => {
                    p.width = Some(attr.parse_and_validate(
                        value,
                        LengthDir::Horizontal,
                        Length::check_nonnegative,
                    )?)
                }

                Attribute::Height => {
                    p.height = Some(attr.parse_and_validate(
                        value,
                        LengthDir::Vertical,
                        Length::check_nonnegative,
                    )?)
                }

                _ => (),
            }
        }

        Ok(())
    }
}

impl PaintSource for NodePattern {
    type Source = Pattern;

    fn resolve(
        &self,
        node: &RsvgNode,
        draw_ctx: &mut DrawingCtx,
        _bbox: &BoundingBox,
    ) -> Result<Option<Self::Source>, RenderingError> {
        let node_pattern = node.get_impl::<NodePattern>().unwrap();
        let pattern = &*node_pattern.pattern.borrow();
        let mut result = pattern.clone();
        let mut stack = NodeStack::new();

        while !result.is_resolved() {
            if let Some(acquired) =
                draw_ctx.get_acquired_node_of_type(result.fallback.as_ref(), NodeType::Pattern)
            {
                let a_node = acquired.get();

                if stack.contains(a_node) {
                    rsvg_log!(
                        "circular reference in pattern {}",
                        node.get_human_readable_name()
                    );
                    return Err(RenderingError::CircularReference);
                }

                a_node.with_impl(|i: &NodePattern| {
                    result.resolve_from_fallback(&*i.pattern.borrow())
                });

                stack.push(a_node);
            } else {
                result.resolve_from_defaults();
            }
        }

        Ok(Some(result))
    }

    fn set_pattern_on_draw_context(
        &self,
        pattern: &Self::Source,
        values: &ComputedValues,
        draw_ctx: &mut DrawingCtx,
        _opacity: &UnitInterval,
        bbox: &BoundingBox,
    ) -> Result<bool, RenderingError> {
        assert!(pattern.is_resolved());

        if pattern.node.is_none() {
            // This means we didn't find any children among the fallbacks,
            // so there is nothing to render.
            return Ok(false);
        }

        let units = pattern.units.unwrap();
        let content_units = pattern.content_units.unwrap();
        let pattern_affine = pattern.affine.unwrap();
        let vbox = pattern.vbox.unwrap();
        let preserve_aspect_ratio = pattern.preserve_aspect_ratio.unwrap();

        let (pattern_x, pattern_y, pattern_width, pattern_height) = {
            let params = if units == PatternUnits(CoordUnits::ObjectBoundingBox) {
                draw_ctx.push_view_box(1.0, 1.0)
            } else {
                draw_ctx.get_view_params()
            };

            let pattern_x = pattern.x.unwrap().normalize(values, &params);
            let pattern_y = pattern.y.unwrap().normalize(values, &params);
            let pattern_width = pattern.width.unwrap().normalize(values, &params);
            let pattern_height = pattern.height.unwrap().normalize(values, &params);

            (pattern_x, pattern_y, pattern_width, pattern_height)
        };

        // Work out the size of the rectangle so it takes into account the object bounding box

        let bbwscale: f64;
        let bbhscale: f64;

        match units {
            PatternUnits(CoordUnits::ObjectBoundingBox) => {
                let bbrect = bbox.rect.unwrap();
                bbwscale = bbrect.width;
                bbhscale = bbrect.height;
            }

            PatternUnits(CoordUnits::UserSpaceOnUse) => {
                bbwscale = 1.0;
                bbhscale = 1.0;
            }
        }

        let cr = draw_ctx.get_cairo_context();
        let affine = cr.get_matrix();
        let taffine = cairo::Matrix::multiply(&pattern_affine, &affine);

        let mut scwscale = (taffine.xx * taffine.xx + taffine.xy * taffine.xy).sqrt();
        let mut schscale = (taffine.yx * taffine.yx + taffine.yy * taffine.yy).sqrt();

        let pw: i32 = (pattern_width * bbwscale * scwscale) as i32;
        let ph: i32 = (pattern_height * bbhscale * schscale) as i32;

        let scaled_width = pattern_width * bbwscale;
        let scaled_height = pattern_height * bbhscale;

        if scaled_width.abs() < f64::EPSILON
            || scaled_height.abs() < f64::EPSILON
            || pw < 1
            || ph < 1
        {
            return Ok(false);
        }

        scwscale = f64::from(pw) / scaled_width;
        schscale = f64::from(ph) / scaled_height;

        let mut affine: cairo::Matrix = cairo::Matrix::identity();

        // Create the pattern coordinate system
        match units {
            PatternUnits(CoordUnits::ObjectBoundingBox) => {
                let bbrect = bbox.rect.unwrap();
                affine.translate(
                    bbrect.x + pattern_x * bbrect.width,
                    bbrect.y + pattern_y * bbrect.height,
                );
            }

            PatternUnits(CoordUnits::UserSpaceOnUse) => {
                affine.translate(pattern_x, pattern_y);
            }
        }

        // Apply the pattern transform
        affine = cairo::Matrix::multiply(&affine, &pattern_affine);

        let mut caffine: cairo::Matrix;

        // Create the pattern contents coordinate system
        let _params = if let Some(vbox) = vbox {
            // If there is a vbox, use that
            let (mut x, mut y, w, h) = preserve_aspect_ratio.compute(
                vbox.0.width,
                vbox.0.height,
                0.0,
                0.0,
                pattern_width * bbwscale,
                pattern_height * bbhscale,
            );

            x -= vbox.0.x * w / vbox.0.width;
            y -= vbox.0.y * h / vbox.0.height;

            caffine = cairo::Matrix::new(w / vbox.0.width, 0.0, 0.0, h / vbox.0.height, x, y);

            draw_ctx.push_view_box(vbox.0.width, vbox.0.height)
        } else if content_units == PatternContentUnits(CoordUnits::ObjectBoundingBox) {
            // If coords are in terms of the bounding box, use them
            let bbrect = bbox.rect.unwrap();

            caffine = cairo::Matrix::identity();
            caffine.scale(bbrect.width, bbrect.height);

            draw_ctx.push_view_box(1.0, 1.0)
        } else {
            caffine = cairo::Matrix::identity();
            draw_ctx.get_view_params()
        };

        if !scwscale.approx_eq_cairo(&1.0) || !schscale.approx_eq_cairo(&1.0) {
            let mut scalematrix = cairo::Matrix::identity();
            scalematrix.scale(scwscale, schscale);
            caffine = cairo::Matrix::multiply(&caffine, &scalematrix);

            scalematrix = cairo::Matrix::identity();
            scalematrix.scale(1.0 / scwscale, 1.0 / schscale);

            affine = cairo::Matrix::multiply(&scalematrix, &affine);
        }

        // Draw to another surface

        let cr_save = draw_ctx.get_cairo_context();

        let surface = cr_save
            .get_target()
            .create_similar(cairo::Content::ColorAlpha, pw, ph);

        let cr_pattern = cairo::Context::new(&surface);

        draw_ctx.set_cairo_context(&cr_pattern);

        // Set up transformations to be determined by the contents units

        // Draw everything
        let pattern_node = pattern.node.clone().unwrap().upgrade().unwrap();
        let pattern_cascaded = pattern_node.get_cascaded_values();
        let pattern_values = pattern_cascaded.get();

        cr_pattern.set_matrix(caffine);

        let res = draw_ctx.with_discrete_layer(&pattern_node, pattern_values, false, &mut |dc| {
            pattern_node.draw_children(&pattern_cascaded, dc, false)
        });

        // Return to the original coordinate system and rendering context

        draw_ctx.set_cairo_context(&cr_save);

        // Set the final surface as a Cairo pattern into the Cairo context

        let surface_pattern = cairo::SurfacePattern::create(&surface);
        surface_pattern.set_extend(cairo::Extend::Repeat);

        let mut matrix = affine;
        matrix.invert();

        surface_pattern.set_matrix(matrix);
        surface_pattern.set_filter(cairo::Filter::Best);

        cr_save.set_source(&cairo::Pattern::SurfacePattern(surface_pattern));

        res.and_then(|_| Ok(true))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn pattern_resolved_from_defaults_is_really_resolved() {
        let mut pat = Pattern::unresolved();

        pat.resolve_from_defaults();
        assert!(pat.is_resolved());
    }
}
<|endoftext|>


<commit_before>use std::collections::HashSet;
use std::collections::hash_map::HashMap;
use std::fmt;
use std::io;
use std::mem;
use std::sync::mpsc::{channel, Sender, Receiver};

use crossbeam::{self, Scope};
use jobserver::{Acquired, HelperThread};

use core::{PackageId, Target, Profile};
use util::{Config, DependencyQueue, Fresh, Dirty, Freshness};
use util::{CargoResult, ProcessBuilder, profile, internal, CargoResultExt};
use {handle_error};

use super::{Context, Kind, Unit};
use super::job::Job;

/// A management structure of the entire dependency graph to compile.
///
/// This structure is backed by the `DependencyQueue` type and manages the
/// actual compilation step of each package. Packages enqueue units of work and
/// then later on the entire graph is processed and compiled.
pub struct JobQueue<'a> {
    queue: DependencyQueue<Key<'a>, Vec<(Job, Freshness)>>,
    tx: Sender<Message<'a>>,
    rx: Receiver<Message<'a>>,
    active: usize,
    pending: HashMap<Key<'a>, PendingBuild>,
    compiled: HashSet<&'a PackageId>,
    documented: HashSet<&'a PackageId>,
    counts: HashMap<&'a PackageId, usize>,
    is_release: bool,
}

/// A helper structure for metadata about the state of a building package.
struct PendingBuild {
    /// Number of jobs currently active
    amt: usize,
    /// Current freshness state of this package. Any dirty target within a
    /// package will cause the entire package to become dirty.
    fresh: Freshness,
}

#[derive(Clone, Copy, Eq, PartialEq, Hash)]
struct Key<'a> {
    pkg: &'a PackageId,
    target: &'a Target,
    profile: &'a Profile,
    kind: Kind,
}

pub struct JobState<'a> {
    tx: Sender<Message<'a>>,
}

enum Message<'a> {
    Run(String),
    Stdout(String),
    Stderr(String),
    Token(io::Result<Acquired>),
    Finish(Key<'a>, CargoResult<()>),
}

impl<'a> JobState<'a> {
    pub fn running(&self, cmd: &ProcessBuilder) {
        let _ = self.tx.send(Message::Run(cmd.to_string()));
    }

    pub fn stdout(&self, out: &str) {
        let _ = self.tx.send(Message::Stdout(out.to_string()));
    }

    pub fn stderr(&self, err: &str) {
        let _ = self.tx.send(Message::Stderr(err.to_string()));
    }
}

impl<'a> JobQueue<'a> {
    pub fn new<'cfg>(cx: &Context<'a, 'cfg>) -> JobQueue<'a> {
        let (tx, rx) = channel();
        JobQueue {
            queue: DependencyQueue::new(),
            tx: tx,
            rx: rx,
            active: 0,
            pending: HashMap::new(),
            compiled: HashSet::new(),
            documented: HashSet::new(),
            counts: HashMap::new(),
            is_release: cx.build_config.release,
        }
    }

    pub fn enqueue<'cfg>(&mut self,
                         cx: &Context<'a, 'cfg>,
                         unit: &Unit<'a>,
                         job: Job,
                         fresh: Freshness) -> CargoResult<()> {
        let key = Key::new(unit);
        let deps = key.dependencies(cx)?;
        self.queue.queue(Fresh, key, Vec::new(), &deps).push((job, fresh));
        *self.counts.entry(key.pkg).or_insert(0) += 1;
        Ok(())
    }

    /// Execute all jobs necessary to build the dependency graph.
    ///
    /// This function will spawn off `config.jobs()` workers to build all of the
    /// necessary dependencies, in order. Freshness is propagated as far as
    /// possible along each dependency chain.
    pub fn execute(&mut self, cx: &mut Context) -> CargoResult<()> {
        let _p = profile::start("executing the job graph");

        // We need to give a handle to the send half of our message queue to the
        // jobserver helper thrad. Unfortunately though we need the handle to be
        // `'static` as that's typically what's required when spawning a
        // thread!
        //
        // To work around this we transmute the `Sender` to a static lifetime.
        // we're only sending "longer living" messages and we should also
        // destroy all references to the channel before this function exits as
        // the destructor for the `helper` object will ensure the associated
        // thread i sno longer running.
        //
        // As a result, this `transmute` to a longer lifetime should be safe in
        // practice.
        let tx = self.tx.clone();
        let tx = unsafe {
            mem::transmute::<Sender<Message<'a>>, Sender<Message<'static>>>(tx)
        };
        let helper = cx.jobserver.clone().into_helper_thread(move |token| {
            drop(tx.send(Message::Token(token)));
        }).chain_err(|| {
            "failed to create helper thread for jobserver management"
        })?;

        crossbeam::scope(|scope| {
            self.drain_the_queue(cx, scope, &helper)
        })
    }

    fn drain_the_queue(&mut self,
                       cx: &mut Context,
                       scope: &Scope<'a>,
                       jobserver_helper: &HelperThread)
                       -> CargoResult<()> {
        use std::time::Instant;

        let mut tokens = Vec::new();
        let mut queue = Vec::new();
        trace!("queue: {:#?}", self.queue);

        // Iteratively execute the entire dependency graph. Each turn of the
        // loop starts out by scheduling as much work as possible (up to the
        // maximum number of parallel jobs we have tokens for). A local queue
        // is maintained separately from the main dependency queue as one
        // dequeue may actually dequeue quite a bit of work (e.g. 10 binaries
        // in one project).
        //
        // After a job has finished we update our internal state if it was
        // successful and otherwise wait for pending work to finish if it failed
        // and then immediately return.
        let mut error = None;
        let start_time = Instant::now();
        loop {
            // Dequeue as much work as we can, learning about everything
            // possible that can run. Note that this is also the point where we
            // start requesting job tokens. Each job after the first needs to
            // request a token.
            while let Some((fresh, key, jobs)) = self.queue.dequeue() {
                let total_fresh = jobs.iter().fold(fresh, |fresh, &(_, f)| {
                    f.combine(fresh)
                });
                self.pending.insert(key, PendingBuild {
                    amt: jobs.len(),
                    fresh: total_fresh,
                });
                for (job, f) in jobs {
                    queue.push((key, job, f.combine(fresh)));
                    if self.active + queue.len() > 0 {
                        jobserver_helper.request_token();
                    }
                }
            }

            // Now that we've learned of all possible work that we can execute
            // try to spawn it so long as we've got a jobserver token which says
            // we're able to perform some parallel work.
            while error.is_none() && self.active < tokens.len() + 1 && !queue.is_empty() {
                let (key, job, fresh) = queue.remove(0);
                self.run(key, fresh, job, cx.config, scope)?;
            }

            // If after all that we're not actually running anything then we're
            // done!
            if self.active == 0 {
                break
            }

            // And finally, before we block waiting for the next event, drop any
            // excess tokens we may have accidentally acquired. Due to how our
            // jobserver interface is architected we may acquire a token that we
            // don't actually use, and if this happens just relinquish it back
            // to the jobserver itself.
            tokens.truncate(self.active - 1);

            match self.rx.recv().unwrap() {
                Message::Run(cmd) => {
                    cx.config.shell().verbose(|c| c.status("Running", &cmd))?;
                }
                Message::Stdout(out) => {
                    if cx.config.extra_verbose() {
                        println!("{}", out);
                    }
                }
                Message::Stderr(err) => {
                    if cx.config.extra_verbose() {
                        writeln!(cx.config.shell().err(), "{}", err)?;
                    }
                }
                Message::Finish(key, result) => {
                    info!("end: {:?}", key);
                    self.active -= 1;
                    if self.active > 0 {
                        assert!(tokens.len() > 0);
                        drop(tokens.pop());
                    }
                    match result {
                        Ok(()) => self.finish(key, cx)?,
                        Err(e) => {
                            let msg = "The following warnings were emitted during compilation:";
                            self.emit_warnings(Some(msg), key, cx)?;

                            if self.active > 0 {
                                error = Some("build failed".into());
                                handle_error(e, &mut *cx.config.shell());
                                cx.config.shell().warn(
                                            "build failed, waiting for other \
                                             jobs to finish...")?;
                            }
                            else {
                                error = Some(e);
                            }
                        }
                    }
                }
                Message::Token(acquired_token) => {
                    tokens.push(acquired_token.chain_err(|| {
                        "failed to acquire jobserver token"
                    })?);
                }
            }
        }

        let build_type = if self.is_release { "release" } else { "dev" };
        let profile = cx.lib_profile();
        let mut opt_type = String::from(if profile.opt_level == "0" { "unoptimized" }
                                        else { "optimized" });
        if profile.debuginfo.is_some() {
            opt_type = opt_type + " + debuginfo";
        }
        let duration = start_time.elapsed();
        let time_elapsed = format!("{}.{1:.2} secs",
                                   duration.as_secs(),
                                   duration.subsec_nanos() / 10000000);
        if self.queue.is_empty() {
            let message = format!("{} [{}] target(s) in {}",
                                  build_type,
                                  opt_type,
                                  time_elapsed);
            cx.config.shell().status("Finished", message)?;
            Ok(())
        } else if let Some(e) = error {
            Err(e)
        } else {
            debug!("queue: {:#?}", self.queue);
            Err(internal("finished with jobs still left in the queue"))
        }
    }

    /// Executes a job in the `scope` given, pushing the spawned thread's
    /// handled onto `threads`.
    fn run(&mut self,
           key: Key<'a>,
           fresh: Freshness,
           job: Job,
           config: &Config,
           scope: &Scope<'a>) -> CargoResult<()> {
        info!("start: {:?}", key);

        self.active += 1;
        *self.counts.get_mut(key.pkg).unwrap() -= 1;

        let my_tx = self.tx.clone();
        let doit = move || {
            let res = job.run(fresh, &JobState {
                tx: my_tx.clone(),
            });
            my_tx.send(Message::Finish(key, res)).unwrap();
        };
        match fresh {
            Freshness::Fresh => doit(),
            Freshness::Dirty => { scope.spawn(doit); }
        }

        // Print out some nice progress information
        self.note_working_on(config, &key, fresh)?;

        Ok(())
    }

    fn emit_warnings(&self, msg: Option<&str>, key: Key<'a>, cx: &mut Context) -> CargoResult<()> {
        let output = cx.build_state.outputs.lock().unwrap();
        if let Some(output) = output.get(&(key.pkg.clone(), key.kind)) {
            if let Some(msg) = msg {
                if !output.warnings.is_empty() {
                    writeln!(cx.config.shell().err(), "{}\n", msg)?;
                }
            }

            for warning in output.warnings.iter() {
                cx.config.shell().warn(warning)?;
            }

            if !output.warnings.is_empty() && msg.is_some() {
                // Output an empty line.
                writeln!(cx.config.shell().err(), "")?;
            }
        }

        Ok(())
    }

    fn finish(&mut self, key: Key<'a>, cx: &mut Context) -> CargoResult<()> {
        if key.profile.run_custom_build && cx.show_warnings(key.pkg) {
            self.emit_warnings(None, key, cx)?;
        }

        let state = self.pending.get_mut(&key).unwrap();
        state.amt -= 1;
        if state.amt == 0 {
            self.queue.finish(&key, state.fresh);
        }
        Ok(())
    }

    // This isn't super trivial because we don't want to print loads and
    // loads of information to the console, but we also want to produce a
    // faithful representation of what's happening. This is somewhat nuanced
    // as a package can start compiling *very* early on because of custom
    // build commands and such.
    //
    // In general, we try to print "Compiling" for the first nontrivial task
    // run for a package, regardless of when that is. We then don't print
    // out any more information for a package after we've printed it once.
    fn note_working_on(&mut self,
                       config: &Config,
                       key: &Key<'a>,
                       fresh: Freshness) -> CargoResult<()> {
        if (self.compiled.contains(key.pkg) && !key.profile.doc) ||
            (self.documented.contains(key.pkg) && key.profile.doc) {
            return Ok(())
        }

        match fresh {
            // Any dirty stage which runs at least one command gets printed as
            // being a compiled package
            Dirty => {
                if key.profile.doc {
                    if !key.profile.test {
                        self.documented.insert(key.pkg);
                        config.shell().status("Documenting", key.pkg)?;
                    }
                } else {
                    self.compiled.insert(key.pkg);
                    config.shell().status("Compiling", key.pkg)?;
                }
            }
            Fresh if self.counts[key.pkg] == 0 => {
                self.compiled.insert(key.pkg);
                config.shell().verbose(|c| c.status("Fresh", key.pkg))?;
            }
            Fresh => {}
        }
        Ok(())
    }
}

impl<'a> Key<'a> {
    fn new(unit: &Unit<'a>) -> Key<'a> {
        Key {
            pkg: unit.pkg.package_id(),
            target: unit.target,
            profile: unit.profile,
            kind: unit.kind,
        }
    }

    fn dependencies<'cfg>(&self, cx: &Context<'a, 'cfg>)
                          -> CargoResult<Vec<Key<'a>>> {
        let unit = Unit {
            pkg: cx.get_package(self.pkg)?,
            target: self.target,
            profile: self.profile,
            kind: self.kind,
        };
        let targets = cx.dep_targets(&unit)?;
        Ok(targets.iter().filter_map(|unit| {
            // Binaries aren't actually needed to *compile* tests, just to run
            // them, so we don't include this dependency edge in the job graph.
            if self.target.is_test() && unit.target.is_bin() {
                None
            } else {
                Some(Key::new(unit))
            }
        }).collect())
    }
}

impl<'a> fmt::Debug for Key<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} => {}/{} => {:?}", self.pkg, self.target, self.profile,
               self.kind)
    }
}
<commit_msg>Auto merge of #4185 - ids1024:typo, r=Mark-Simulacrum<commit_after>use std::collections::HashSet;
use std::collections::hash_map::HashMap;
use std::fmt;
use std::io;
use std::mem;
use std::sync::mpsc::{channel, Sender, Receiver};

use crossbeam::{self, Scope};
use jobserver::{Acquired, HelperThread};

use core::{PackageId, Target, Profile};
use util::{Config, DependencyQueue, Fresh, Dirty, Freshness};
use util::{CargoResult, ProcessBuilder, profile, internal, CargoResultExt};
use {handle_error};

use super::{Context, Kind, Unit};
use super::job::Job;

/// A management structure of the entire dependency graph to compile.
///
/// This structure is backed by the `DependencyQueue` type and manages the
/// actual compilation step of each package. Packages enqueue units of work and
/// then later on the entire graph is processed and compiled.
pub struct JobQueue<'a> {
    queue: DependencyQueue<Key<'a>, Vec<(Job, Freshness)>>,
    tx: Sender<Message<'a>>,
    rx: Receiver<Message<'a>>,
    active: usize,
    pending: HashMap<Key<'a>, PendingBuild>,
    compiled: HashSet<&'a PackageId>,
    documented: HashSet<&'a PackageId>,
    counts: HashMap<&'a PackageId, usize>,
    is_release: bool,
}

/// A helper structure for metadata about the state of a building package.
struct PendingBuild {
    /// Number of jobs currently active
    amt: usize,
    /// Current freshness state of this package. Any dirty target within a
    /// package will cause the entire package to become dirty.
    fresh: Freshness,
}

#[derive(Clone, Copy, Eq, PartialEq, Hash)]
struct Key<'a> {
    pkg: &'a PackageId,
    target: &'a Target,
    profile: &'a Profile,
    kind: Kind,
}

pub struct JobState<'a> {
    tx: Sender<Message<'a>>,
}

enum Message<'a> {
    Run(String),
    Stdout(String),
    Stderr(String),
    Token(io::Result<Acquired>),
    Finish(Key<'a>, CargoResult<()>),
}

impl<'a> JobState<'a> {
    pub fn running(&self, cmd: &ProcessBuilder) {
        let _ = self.tx.send(Message::Run(cmd.to_string()));
    }

    pub fn stdout(&self, out: &str) {
        let _ = self.tx.send(Message::Stdout(out.to_string()));
    }

    pub fn stderr(&self, err: &str) {
        let _ = self.tx.send(Message::Stderr(err.to_string()));
    }
}

impl<'a> JobQueue<'a> {
    pub fn new<'cfg>(cx: &Context<'a, 'cfg>) -> JobQueue<'a> {
        let (tx, rx) = channel();
        JobQueue {
            queue: DependencyQueue::new(),
            tx: tx,
            rx: rx,
            active: 0,
            pending: HashMap::new(),
            compiled: HashSet::new(),
            documented: HashSet::new(),
            counts: HashMap::new(),
            is_release: cx.build_config.release,
        }
    }

    pub fn enqueue<'cfg>(&mut self,
                         cx: &Context<'a, 'cfg>,
                         unit: &Unit<'a>,
                         job: Job,
                         fresh: Freshness) -> CargoResult<()> {
        let key = Key::new(unit);
        let deps = key.dependencies(cx)?;
        self.queue.queue(Fresh, key, Vec::new(), &deps).push((job, fresh));
        *self.counts.entry(key.pkg).or_insert(0) += 1;
        Ok(())
    }

    /// Execute all jobs necessary to build the dependency graph.
    ///
    /// This function will spawn off `config.jobs()` workers to build all of the
    /// necessary dependencies, in order. Freshness is propagated as far as
    /// possible along each dependency chain.
    pub fn execute(&mut self, cx: &mut Context) -> CargoResult<()> {
        let _p = profile::start("executing the job graph");

        // We need to give a handle to the send half of our message queue to the
        // jobserver helper thread. Unfortunately though we need the handle to be
        // `'static` as that's typically what's required when spawning a
        // thread!
        //
        // To work around this we transmute the `Sender` to a static lifetime.
        // we're only sending "longer living" messages and we should also
        // destroy all references to the channel before this function exits as
        // the destructor for the `helper` object will ensure the associated
        // thread i sno longer running.
        //
        // As a result, this `transmute` to a longer lifetime should be safe in
        // practice.
        let tx = self.tx.clone();
        let tx = unsafe {
            mem::transmute::<Sender<Message<'a>>, Sender<Message<'static>>>(tx)
        };
        let helper = cx.jobserver.clone().into_helper_thread(move |token| {
            drop(tx.send(Message::Token(token)));
        }).chain_err(|| {
            "failed to create helper thread for jobserver management"
        })?;

        crossbeam::scope(|scope| {
            self.drain_the_queue(cx, scope, &helper)
        })
    }

    fn drain_the_queue(&mut self,
                       cx: &mut Context,
                       scope: &Scope<'a>,
                       jobserver_helper: &HelperThread)
                       -> CargoResult<()> {
        use std::time::Instant;

        let mut tokens = Vec::new();
        let mut queue = Vec::new();
        trace!("queue: {:#?}", self.queue);

        // Iteratively execute the entire dependency graph. Each turn of the
        // loop starts out by scheduling as much work as possible (up to the
        // maximum number of parallel jobs we have tokens for). A local queue
        // is maintained separately from the main dependency queue as one
        // dequeue may actually dequeue quite a bit of work (e.g. 10 binaries
        // in one project).
        //
        // After a job has finished we update our internal state if it was
        // successful and otherwise wait for pending work to finish if it failed
        // and then immediately return.
        let mut error = None;
        let start_time = Instant::now();
        loop {
            // Dequeue as much work as we can, learning about everything
            // possible that can run. Note that this is also the point where we
            // start requesting job tokens. Each job after the first needs to
            // request a token.
            while let Some((fresh, key, jobs)) = self.queue.dequeue() {
                let total_fresh = jobs.iter().fold(fresh, |fresh, &(_, f)| {
                    f.combine(fresh)
                });
                self.pending.insert(key, PendingBuild {
                    amt: jobs.len(),
                    fresh: total_fresh,
                });
                for (job, f) in jobs {
                    queue.push((key, job, f.combine(fresh)));
                    if self.active + queue.len() > 0 {
                        jobserver_helper.request_token();
                    }
                }
            }

            // Now that we've learned of all possible work that we can execute
            // try to spawn it so long as we've got a jobserver token which says
            // we're able to perform some parallel work.
            while error.is_none() && self.active < tokens.len() + 1 && !queue.is_empty() {
                let (key, job, fresh) = queue.remove(0);
                self.run(key, fresh, job, cx.config, scope)?;
            }

            // If after all that we're not actually running anything then we're
            // done!
            if self.active == 0 {
                break
            }

            // And finally, before we block waiting for the next event, drop any
            // excess tokens we may have accidentally acquired. Due to how our
            // jobserver interface is architected we may acquire a token that we
            // don't actually use, and if this happens just relinquish it back
            // to the jobserver itself.
            tokens.truncate(self.active - 1);

            match self.rx.recv().unwrap() {
                Message::Run(cmd) => {
                    cx.config.shell().verbose(|c| c.status("Running", &cmd))?;
                }
                Message::Stdout(out) => {
                    if cx.config.extra_verbose() {
                        println!("{}", out);
                    }
                }
                Message::Stderr(err) => {
                    if cx.config.extra_verbose() {
                        writeln!(cx.config.shell().err(), "{}", err)?;
                    }
                }
                Message::Finish(key, result) => {
                    info!("end: {:?}", key);
                    self.active -= 1;
                    if self.active > 0 {
                        assert!(tokens.len() > 0);
                        drop(tokens.pop());
                    }
                    match result {
                        Ok(()) => self.finish(key, cx)?,
                        Err(e) => {
                            let msg = "The following warnings were emitted during compilation:";
                            self.emit_warnings(Some(msg), key, cx)?;

                            if self.active > 0 {
                                error = Some("build failed".into());
                                handle_error(e, &mut *cx.config.shell());
                                cx.config.shell().warn(
                                            "build failed, waiting for other \
                                             jobs to finish...")?;
                            }
                            else {
                                error = Some(e);
                            }
                        }
                    }
                }
                Message::Token(acquired_token) => {
                    tokens.push(acquired_token.chain_err(|| {
                        "failed to acquire jobserver token"
                    })?);
                }
            }
        }

        let build_type = if self.is_release { "release" } else { "dev" };
        let profile = cx.lib_profile();
        let mut opt_type = String::from(if profile.opt_level == "0" { "unoptimized" }
                                        else { "optimized" });
        if profile.debuginfo.is_some() {
            opt_type = opt_type + " + debuginfo";
        }
        let duration = start_time.elapsed();
        let time_elapsed = format!("{}.{1:.2} secs",
                                   duration.as_secs(),
                                   duration.subsec_nanos() / 10000000);
        if self.queue.is_empty() {
            let message = format!("{} [{}] target(s) in {}",
                                  build_type,
                                  opt_type,
                                  time_elapsed);
            cx.config.shell().status("Finished", message)?;
            Ok(())
        } else if let Some(e) = error {
            Err(e)
        } else {
            debug!("queue: {:#?}", self.queue);
            Err(internal("finished with jobs still left in the queue"))
        }
    }

    /// Executes a job in the `scope` given, pushing the spawned thread's
    /// handled onto `threads`.
    fn run(&mut self,
           key: Key<'a>,
           fresh: Freshness,
           job: Job,
           config: &Config,
           scope: &Scope<'a>) -> CargoResult<()> {
        info!("start: {:?}", key);

        self.active += 1;
        *self.counts.get_mut(key.pkg).unwrap() -= 1;

        let my_tx = self.tx.clone();
        let doit = move || {
            let res = job.run(fresh, &JobState {
                tx: my_tx.clone(),
            });
            my_tx.send(Message::Finish(key, res)).unwrap();
        };
        match fresh {
            Freshness::Fresh => doit(),
            Freshness::Dirty => { scope.spawn(doit); }
        }

        // Print out some nice progress information
        self.note_working_on(config, &key, fresh)?;

        Ok(())
    }

    fn emit_warnings(&self, msg: Option<&str>, key: Key<'a>, cx: &mut Context) -> CargoResult<()> {
        let output = cx.build_state.outputs.lock().unwrap();
        if let Some(output) = output.get(&(key.pkg.clone(), key.kind)) {
            if let Some(msg) = msg {
                if !output.warnings.is_empty() {
                    writeln!(cx.config.shell().err(), "{}\n", msg)?;
                }
            }

            for warning in output.warnings.iter() {
                cx.config.shell().warn(warning)?;
            }

            if !output.warnings.is_empty() && msg.is_some() {
                // Output an empty line.
                writeln!(cx.config.shell().err(), "")?;
            }
        }

        Ok(())
    }

    fn finish(&mut self, key: Key<'a>, cx: &mut Context) -> CargoResult<()> {
        if key.profile.run_custom_build && cx.show_warnings(key.pkg) {
            self.emit_warnings(None, key, cx)?;
        }

        let state = self.pending.get_mut(&key).unwrap();
        state.amt -= 1;
        if state.amt == 0 {
            self.queue.finish(&key, state.fresh);
        }
        Ok(())
    }

    // This isn't super trivial because we don't want to print loads and
    // loads of information to the console, but we also want to produce a
    // faithful representation of what's happening. This is somewhat nuanced
    // as a package can start compiling *very* early on because of custom
    // build commands and such.
    //
    // In general, we try to print "Compiling" for the first nontrivial task
    // run for a package, regardless of when that is. We then don't print
    // out any more information for a package after we've printed it once.
    fn note_working_on(&mut self,
                       config: &Config,
                       key: &Key<'a>,
                       fresh: Freshness) -> CargoResult<()> {
        if (self.compiled.contains(key.pkg) && !key.profile.doc) ||
            (self.documented.contains(key.pkg) && key.profile.doc) {
            return Ok(())
        }

        match fresh {
            // Any dirty stage which runs at least one command gets printed as
            // being a compiled package
            Dirty => {
                if key.profile.doc {
                    if !key.profile.test {
                        self.documented.insert(key.pkg);
                        config.shell().status("Documenting", key.pkg)?;
                    }
                } else {
                    self.compiled.insert(key.pkg);
                    config.shell().status("Compiling", key.pkg)?;
                }
            }
            Fresh if self.counts[key.pkg] == 0 => {
                self.compiled.insert(key.pkg);
                config.shell().verbose(|c| c.status("Fresh", key.pkg))?;
            }
            Fresh => {}
        }
        Ok(())
    }
}

impl<'a> Key<'a> {
    fn new(unit: &Unit<'a>) -> Key<'a> {
        Key {
            pkg: unit.pkg.package_id(),
            target: unit.target,
            profile: unit.profile,
            kind: unit.kind,
        }
    }

    fn dependencies<'cfg>(&self, cx: &Context<'a, 'cfg>)
                          -> CargoResult<Vec<Key<'a>>> {
        let unit = Unit {
            pkg: cx.get_package(self.pkg)?,
            target: self.target,
            profile: self.profile,
            kind: self.kind,
        };
        let targets = cx.dep_targets(&unit)?;
        Ok(targets.iter().filter_map(|unit| {
            // Binaries aren't actually needed to *compile* tests, just to run
            // them, so we don't include this dependency edge in the job graph.
            if self.target.is_test() && unit.target.is_bin() {
                None
            } else {
                Some(Key::new(unit))
            }
        }).collect())
    }
}

impl<'a> fmt::Debug for Key<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} => {}/{} => {:?}", self.pkg, self.target, self.profile,
               self.kind)
    }
}
<|endoftext|>