<commit_before>use ::cssparser::{Parser, ParserInput, Token};
use ::glib::translate::*;
use ::libc;

use std::f64::consts::*;

use drawing_ctx;
use drawing_ctx::RsvgDrawingCtx;
use parsers::Parse;
use parsers::ParseError;
use error::*;

/* Keep this in sync with ../../rsvg-private.h:LengthUnit */
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum LengthUnit {
    Default,
    Percent,
    FontEm,
    FontEx,
    Inch,
    RelativeLarger,
    RelativeSmaller
}

/* Keep this in sync with ../../rsvg-private.h:LengthDir */
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum LengthDir {
    Horizontal,
    Vertical,
    Both
}

/* This is *not* an opaque struct; it is actually visible to the C code.  It is so
 * that the remaining C code can create RsvgLength values as part of existing
 * structures or objects, without allocations on the heap.
 */
/* Keep this in sync with ../../rsvg-private.h:RsvgLength */
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct RsvgLength {
    pub length: f64,
    pub unit: LengthUnit,
    dir: LengthDir
}

impl Default for RsvgLength {
    fn default () -> RsvgLength {
        RsvgLength {
            length: 0.0,
            unit:   LengthUnit::Default,
            dir:    LengthDir::Both
        }
    }
}

const POINTS_PER_INCH: f64 = 72.0;
const CM_PER_INCH:     f64 = 2.54;
const MM_PER_INCH:     f64 = 25.4;
const PICA_PER_INCH:   f64 = 6.0;

fn compute_named_size (name: &str) -> f64 {
    let power: f64;

    match name {
        "xx-small" => { power = -3.0; },
        "x-small"  => { power = -2.0; },
        "small"    => { power = -1.0; },
        "medium"   => { power = 0.0; },
        "large"    => { power = 1.0; },
        "x-large"  => { power = 2.0; },
        "xx-large" => { power = 3.0; },
        _          => { unreachable! (); }
    }

    12.0 * 1.2f64.powf (power) / POINTS_PER_INCH
}

#[no_mangle]
pub extern fn rsvg_length_parse (string: *const libc::c_char, dir: LengthDir) -> RsvgLength {
    let my_string = unsafe { &String::from_glib_none (string) };

    // FIXME: this ignores errors; propagate them upstream
    RsvgLength::parse (my_string, dir).unwrap_or_else(|_| {RsvgLength::default ()})
}

/* https://www.w3.org/TR/SVG/types.html#DataTypeLength
 * https://www.w3.org/TR/2008/REC-CSS2-20080411/syndata.html#length-units
 *
 * Lengths have units.  When they need to be need resolved to
 * units in the user's coordinate system, some unit types
 * need to know if they are horizontal/vertical/both.  For example,
 * a some_object.width="50%" is 50% with respect to the current
 * viewport's width.  In this case, the @dir argument is used
 * inside RsvgLength::normalize(), when it needs to know to what the
 * length refers.
 */

fn make_err () -> AttributeError {
    AttributeError::Parse (ParseError::new ("expected length: number(\"em\" | \"ex\" | \"px\" | \"in\" | \"cm\" | \"mm\" | \"pt\" | \"pc\" | \"%\")?"))
}

impl Parse for RsvgLength {
    type Data = LengthDir;
    type Err = AttributeError;

    fn parse (string: &str, dir: LengthDir) -> Result <RsvgLength, AttributeError> {
        let mut input = ParserInput::new (string);
        let mut parser = Parser::new (&mut input);

        let length = RsvgLength::from_cssparser(&mut parser, dir)?;

        parser.expect_exhausted ().map_err (|_| make_err ())?;

        Ok(length)
    }
}

impl RsvgLength {
    pub fn new(l: f64, unit: LengthUnit, dir: LengthDir) -> RsvgLength {
        RsvgLength {
            length: l,
            unit: unit,
            dir: dir
        }
    }

    pub fn check_nonnegative (self) -> Result <RsvgLength, AttributeError> {
        if self.length >= 0.0 {
            Ok (self)
        } else {
            Err (AttributeError::Value ("value must be non-negative".to_string ()))
        }
    }

    pub fn normalize (&self, draw_ctx: *const RsvgDrawingCtx) -> f64 {
        match self.unit {
            LengthUnit::Default => {
                self.length
            },

            LengthUnit::Percent => {
                let (width, height) = drawing_ctx::get_view_box_size (draw_ctx);

                match self.dir {
                    LengthDir::Horizontal => { self.length * width },
                    LengthDir::Vertical   => { self.length * height },
                    LengthDir::Both       => { self.length * viewport_percentage (width, height) }
                }
            },

            LengthUnit::FontEm => {
                self.length * drawing_ctx::get_normalized_font_size (draw_ctx)
            },

            LengthUnit::FontEx => {
                self.length * drawing_ctx::get_normalized_font_size (draw_ctx) / 2.0
            },

            LengthUnit::Inch => {
                let (dpi_x, dpi_y) = drawing_ctx::get_dpi (draw_ctx);

                match self.dir {
                    LengthDir::Horizontal => { self.length * dpi_x },
                    LengthDir::Vertical   => { self.length * dpi_y },
                    LengthDir::Both       => { self.length * viewport_percentage (dpi_x, dpi_y) }
                }
            },

            // FIXME: these are pending: https://www.w3.org/TR/2008/REC-CSS2-20080411/fonts.html#propdef-font-size
            LengthUnit::RelativeLarger |
            LengthUnit::RelativeSmaller => { 0.0 }
        }
    }

    pub fn hand_normalize (&self,
                           pixels_per_inch: f64,
                           width_or_height: f64,
                           font_size: f64) -> f64 {
        match self.unit {
            LengthUnit::Default => { self.length },

            LengthUnit::Percent => { self.length * width_or_height },

            LengthUnit::FontEm => { self.length * font_size },

            LengthUnit::FontEx => { self.length * font_size / 2.0 },

            LengthUnit::Inch => { self.length * pixels_per_inch },

            _ => { 0.0 }
        }
    }

    fn from_cssparser(parser: &mut Parser, dir: LengthDir) -> Result <RsvgLength, AttributeError> {

        let length = {
            let token = parser.next ()
                .map_err (|_| AttributeError::Parse (ParseError::new ("expected number and optional symbol, or number and percentage")))?;

            match *token {
                Token::Number { value, .. } => RsvgLength { length: f64::from(value),
                                                            unit:   LengthUnit::Default,
                                                            dir:    dir },

                Token::Percentage { unit_value, .. } => RsvgLength { length: f64::from(unit_value),
                                                                     unit:   LengthUnit::Percent,
                                                                     dir:    dir },

                Token::Dimension { value, ref unit, .. } => {
                    let value = f64::from(value);

                    match unit.as_ref () {
                        "em" => RsvgLength { length: value,
                                             unit:   LengthUnit::FontEm,
                                             dir:    dir },

                        "ex" => RsvgLength { length: value,
                                             unit:   LengthUnit::FontEx,
                                             dir:    dir },

                        "pt" => RsvgLength { length: value / POINTS_PER_INCH,
                                             unit:   LengthUnit::Inch,
                                             dir:    dir },

                        "in" => RsvgLength { length: value,
                                             unit:   LengthUnit::Inch,
                                             dir:    dir },

                        "cm" => RsvgLength { length: value / CM_PER_INCH,
                                             unit:   LengthUnit::Inch,
                                             dir:    dir },

                        "mm" => RsvgLength { length: value / MM_PER_INCH,
                                             unit:   LengthUnit::Inch,
                                             dir:    dir },

                        "pc" => RsvgLength { length: value / PICA_PER_INCH,
                                             unit:   LengthUnit::Inch,
                                             dir:    dir },

                        "px" => RsvgLength { length: value,
                                             unit:   LengthUnit::Default,
                                             dir:    dir },

                        _ => return Err (make_err ())
                    }
                },

                // FIXME: why are the following in Length?  They should be in FontSize
                Token::Ident (ref cow) => match cow.as_ref () {
                    "larger" => RsvgLength { length: 0.0,
                                             unit:   LengthUnit::RelativeLarger,
                                             dir:    dir },

                    "smaller" => RsvgLength { length: 0.0,
                                              unit:  LengthUnit::RelativeSmaller,
                                              dir:   dir },

                    "xx-small" |
                    "x-small" |
                    "small" |
                    "medium" |
                    "large" |
                    "x-large" |
                    "xx-large" => RsvgLength { length: compute_named_size (cow),
                                               unit:   LengthUnit::Inch,
                                               dir:    dir },

                    _ => return Err (make_err ())
                },

                _ => return Err (make_err ())
            }
        };

        Ok (length)
    }
}

fn viewport_percentage (x: f64, y: f64) -> f64 {
    /* https://www.w3.org/TR/SVG/coords.html#Units
     *
     * "For any other length value expressed as a percentage of the viewport, the
     * percentage is calculated as the specified percentage of
     * sqrt((actual-width)**2 + (actual-height)**2))/sqrt(2)."
     */
    (x * x + y * y).sqrt () / SQRT_2
}

#[no_mangle]
pub extern fn rsvg_length_normalize (raw_length: *const RsvgLength, draw_ctx: *const RsvgDrawingCtx) -> f64 {
    assert! (!raw_length.is_null ());

    let length: &RsvgLength = unsafe { &*raw_length };

    length.normalize (draw_ctx)
}

#[no_mangle]
pub extern fn rsvg_length_hand_normalize (raw_length: *const RsvgLength,
                                          pixels_per_inch: f64,
                                          width_or_height: f64,
                                          font_size: f64) -> f64 {
    assert! (!raw_length.is_null ());

    let length: &RsvgLength = unsafe { &*raw_length };

    length.hand_normalize (pixels_per_inch, width_or_height, font_size)
}

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

    #[test]
    fn parses_default () {
        assert_eq! (RsvgLength::parse ("42", LengthDir::Horizontal),
                    Ok (RsvgLength::new(42.0, LengthUnit::Default, LengthDir::Horizontal)));

        assert_eq! (RsvgLength::parse ("-42px", LengthDir::Horizontal),
                    Ok (RsvgLength::new(-42.0, LengthUnit::Default, LengthDir::Horizontal)));
    }

    #[test]
    fn parses_percent () {
        assert_eq! (RsvgLength::parse ("50.0%", LengthDir::Horizontal),
                    Ok (RsvgLength::new(0.5, LengthUnit::Percent, LengthDir::Horizontal)));
    }

    #[test]
    fn parses_font_em () {
        assert_eq! (RsvgLength::parse ("22.5em", LengthDir::Vertical),
                    Ok (RsvgLength::new(22.5, LengthUnit::FontEm, LengthDir::Vertical)));
    }

    #[test]
    fn parses_font_ex () {
        assert_eq! (RsvgLength::parse ("22.5ex", LengthDir::Vertical),
                    Ok (RsvgLength::new(22.5, LengthUnit::FontEx, LengthDir::Vertical)));
    }

    #[test]
    fn parses_physical_units () {
        assert_eq! (RsvgLength::parse ("72pt", LengthDir::Both),
                    Ok (RsvgLength::new(1.0, LengthUnit::Inch, LengthDir::Both)));

        assert_eq! (RsvgLength::parse ("-22.5in", LengthDir::Both),
                    Ok (RsvgLength::new(-22.5, LengthUnit::Inch, LengthDir::Both)));

        assert_eq! (RsvgLength::parse ("-254cm", LengthDir::Both),
                    Ok (RsvgLength::new(-100.0, LengthUnit::Inch, LengthDir::Both)));

        assert_eq! (RsvgLength::parse ("254mm", LengthDir::Both),
                    Ok (RsvgLength::new(10.0, LengthUnit::Inch, LengthDir::Both)));

        assert_eq! (RsvgLength::parse ("60pc", LengthDir::Both),
                    Ok (RsvgLength::new(10.0, LengthUnit::Inch, LengthDir::Both)));
    }

    #[test]
    fn parses_relative_larger () {
        assert_eq! (RsvgLength::parse ("larger", LengthDir::Vertical),
                    Ok (RsvgLength::new(0.0, LengthUnit::RelativeLarger, LengthDir::Vertical)));
    }

    #[test]
    fn parses_relative_smaller () {
        assert_eq! (RsvgLength::parse ("smaller", LengthDir::Vertical),
                    Ok (RsvgLength::new(0.0, LengthUnit::RelativeSmaller, LengthDir::Vertical)));
    }

    #[test]
    fn parses_named_sizes () {
        let names = vec![ "xx-small",
                           "x-small",
                           "small",
                           "medium",
                           "large",
                           "x-large",
                           "xx-large" ];

        let mut previous_value: Option<f64> = None;

        // Just ensure that the values are progressively larger; don't
        // enforce a particular sequence.

        for name in names {
            let length = RsvgLength::parse (name, LengthDir::Both).unwrap ();

            assert_eq! (length.unit, LengthUnit::Inch);
            assert_eq! (length.dir, LengthDir::Both);

            if let Some (v) = previous_value {
                assert! (length.length > v);
            } else {
                previous_value = Some (length.length);
            }

        }
    }

    #[test]
    fn empty_length_yields_error () {
        assert! (is_parse_error (&RsvgLength::parse ("", LengthDir::Both)));
    }

    #[test]
    fn invalid_unit_yields_error () {
        assert! (is_parse_error (&RsvgLength::parse ("8furlong", LengthDir::Both)));
    }

    #[test]
    fn invalid_font_size_yields_error () {
        // FIXME: this is intended to test the (absence of) the "larger" et al values.
        // Since they really be in FontSize, not RsvgLength, we should remember
        // to move this test to that type later.
        assert! (is_parse_error (&RsvgLength::parse ("furlong", LengthDir::Both)));
    }

    #[test]
    fn check_nonnegative_works () {
        assert! (RsvgLength::parse ("0", LengthDir::Both).and_then (|l| l.check_nonnegative ()).is_ok ());
        assert! (RsvgLength::parse ("-10", LengthDir::Both).and_then (|l| l.check_nonnegative ()).is_err ());
    }
}
<commit_msg>rsvg_length_parse(): Don't copy the incoming string<commit_after>use ::cssparser::{Parser, ParserInput, Token};
use ::glib::translate::*;
use ::libc;

use std::f64::consts::*;

use drawing_ctx;
use drawing_ctx::RsvgDrawingCtx;
use parsers::Parse;
use parsers::ParseError;
use error::*;
use util::utf8_cstr;

/* Keep this in sync with ../../rsvg-private.h:LengthUnit */
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum LengthUnit {
    Default,
    Percent,
    FontEm,
    FontEx,
    Inch,
    RelativeLarger,
    RelativeSmaller
}

/* Keep this in sync with ../../rsvg-private.h:LengthDir */
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum LengthDir {
    Horizontal,
    Vertical,
    Both
}

/* This is *not* an opaque struct; it is actually visible to the C code.  It is so
 * that the remaining C code can create RsvgLength values as part of existing
 * structures or objects, without allocations on the heap.
 */
/* Keep this in sync with ../../rsvg-private.h:RsvgLength */
#[repr(C)]
#[derive(Debug, PartialEq, Copy, Clone)]
pub struct RsvgLength {
    pub length: f64,
    pub unit: LengthUnit,
    dir: LengthDir
}

impl Default for RsvgLength {
    fn default () -> RsvgLength {
        RsvgLength {
            length: 0.0,
            unit:   LengthUnit::Default,
            dir:    LengthDir::Both
        }
    }
}

const POINTS_PER_INCH: f64 = 72.0;
const CM_PER_INCH:     f64 = 2.54;
const MM_PER_INCH:     f64 = 25.4;
const PICA_PER_INCH:   f64 = 6.0;

fn compute_named_size (name: &str) -> f64 {
    let power: f64;

    match name {
        "xx-small" => { power = -3.0; },
        "x-small"  => { power = -2.0; },
        "small"    => { power = -1.0; },
        "medium"   => { power = 0.0; },
        "large"    => { power = 1.0; },
        "x-large"  => { power = 2.0; },
        "xx-large" => { power = 3.0; },
        _          => { unreachable! (); }
    }

    12.0 * 1.2f64.powf (power) / POINTS_PER_INCH
}

#[no_mangle]
pub extern fn rsvg_length_parse (string: *const libc::c_char, dir: LengthDir) -> RsvgLength {
    let my_string = unsafe { utf8_cstr(string) };

    // FIXME: this ignores errors; propagate them upstream
    RsvgLength::parse (my_string, dir).unwrap_or_else(|_| {RsvgLength::default ()})
}

/* https://www.w3.org/TR/SVG/types.html#DataTypeLength
 * https://www.w3.org/TR/2008/REC-CSS2-20080411/syndata.html#length-units
 *
 * Lengths have units.  When they need to be need resolved to
 * units in the user's coordinate system, some unit types
 * need to know if they are horizontal/vertical/both.  For example,
 * a some_object.width="50%" is 50% with respect to the current
 * viewport's width.  In this case, the @dir argument is used
 * inside RsvgLength::normalize(), when it needs to know to what the
 * length refers.
 */

fn make_err () -> AttributeError {
    AttributeError::Parse (ParseError::new ("expected length: number(\"em\" | \"ex\" | \"px\" | \"in\" | \"cm\" | \"mm\" | \"pt\" | \"pc\" | \"%\")?"))
}

impl Parse for RsvgLength {
    type Data = LengthDir;
    type Err = AttributeError;

    fn parse (string: &str, dir: LengthDir) -> Result <RsvgLength, AttributeError> {
        let mut input = ParserInput::new (string);
        let mut parser = Parser::new (&mut input);

        let length = RsvgLength::from_cssparser(&mut parser, dir)?;

        parser.expect_exhausted ().map_err (|_| make_err ())?;

        Ok(length)
    }
}

impl RsvgLength {
    pub fn new(l: f64, unit: LengthUnit, dir: LengthDir) -> RsvgLength {
        RsvgLength {
            length: l,
            unit: unit,
            dir: dir
        }
    }

    pub fn check_nonnegative (self) -> Result <RsvgLength, AttributeError> {
        if self.length >= 0.0 {
            Ok (self)
        } else {
            Err (AttributeError::Value ("value must be non-negative".to_string ()))
        }
    }

    pub fn normalize (&self, draw_ctx: *const RsvgDrawingCtx) -> f64 {
        match self.unit {
            LengthUnit::Default => {
                self.length
            },

            LengthUnit::Percent => {
                let (width, height) = drawing_ctx::get_view_box_size (draw_ctx);

                match self.dir {
                    LengthDir::Horizontal => { self.length * width },
                    LengthDir::Vertical   => { self.length * height },
                    LengthDir::Both       => { self.length * viewport_percentage (width, height) }
                }
            },

            LengthUnit::FontEm => {
                self.length * drawing_ctx::get_normalized_font_size (draw_ctx)
            },

            LengthUnit::FontEx => {
                self.length * drawing_ctx::get_normalized_font_size (draw_ctx) / 2.0
            },

            LengthUnit::Inch => {
                let (dpi_x, dpi_y) = drawing_ctx::get_dpi (draw_ctx);

                match self.dir {
                    LengthDir::Horizontal => { self.length * dpi_x },
                    LengthDir::Vertical   => { self.length * dpi_y },
                    LengthDir::Both       => { self.length * viewport_percentage (dpi_x, dpi_y) }
                }
            },

            // FIXME: these are pending: https://www.w3.org/TR/2008/REC-CSS2-20080411/fonts.html#propdef-font-size
            LengthUnit::RelativeLarger |
            LengthUnit::RelativeSmaller => { 0.0 }
        }
    }

    pub fn hand_normalize (&self,
                           pixels_per_inch: f64,
                           width_or_height: f64,
                           font_size: f64) -> f64 {
        match self.unit {
            LengthUnit::Default => { self.length },

            LengthUnit::Percent => { self.length * width_or_height },

            LengthUnit::FontEm => { self.length * font_size },

            LengthUnit::FontEx => { self.length * font_size / 2.0 },

            LengthUnit::Inch => { self.length * pixels_per_inch },

            _ => { 0.0 }
        }
    }

    fn from_cssparser(parser: &mut Parser, dir: LengthDir) -> Result <RsvgLength, AttributeError> {

        let length = {
            let token = parser.next ()
                .map_err (|_| AttributeError::Parse (ParseError::new ("expected number and optional symbol, or number and percentage")))?;

            match *token {
                Token::Number { value, .. } => RsvgLength { length: f64::from(value),
                                                            unit:   LengthUnit::Default,
                                                            dir:    dir },

                Token::Percentage { unit_value, .. } => RsvgLength { length: f64::from(unit_value),
                                                                     unit:   LengthUnit::Percent,
                                                                     dir:    dir },

                Token::Dimension { value, ref unit, .. } => {
                    let value = f64::from(value);

                    match unit.as_ref () {
                        "em" => RsvgLength { length: value,
                                             unit:   LengthUnit::FontEm,
                                             dir:    dir },

                        "ex" => RsvgLength { length: value,
                                             unit:   LengthUnit::FontEx,
                                             dir:    dir },

                        "pt" => RsvgLength { length: value / POINTS_PER_INCH,
                                             unit:   LengthUnit::Inch,
                                             dir:    dir },

                        "in" => RsvgLength { length: value,
                                             unit:   LengthUnit::Inch,
                                             dir:    dir },

                        "cm" => RsvgLength { length: value / CM_PER_INCH,
                                             unit:   LengthUnit::Inch,
                                             dir:    dir },

                        "mm" => RsvgLength { length: value / MM_PER_INCH,
                                             unit:   LengthUnit::Inch,
                                             dir:    dir },

                        "pc" => RsvgLength { length: value / PICA_PER_INCH,
                                             unit:   LengthUnit::Inch,
                                             dir:    dir },

                        "px" => RsvgLength { length: value,
                                             unit:   LengthUnit::Default,
                                             dir:    dir },

                        _ => return Err (make_err ())
                    }
                },

                // FIXME: why are the following in Length?  They should be in FontSize
                Token::Ident (ref cow) => match cow.as_ref () {
                    "larger" => RsvgLength { length: 0.0,
                                             unit:   LengthUnit::RelativeLarger,
                                             dir:    dir },

                    "smaller" => RsvgLength { length: 0.0,
                                              unit:  LengthUnit::RelativeSmaller,
                                              dir:   dir },

                    "xx-small" |
                    "x-small" |
                    "small" |
                    "medium" |
                    "large" |
                    "x-large" |
                    "xx-large" => RsvgLength { length: compute_named_size (cow),
                                               unit:   LengthUnit::Inch,
                                               dir:    dir },

                    _ => return Err (make_err ())
                },

                _ => return Err (make_err ())
            }
        };

        Ok (length)
    }
}

fn viewport_percentage (x: f64, y: f64) -> f64 {
    /* https://www.w3.org/TR/SVG/coords.html#Units
     *
     * "For any other length value expressed as a percentage of the viewport, the
     * percentage is calculated as the specified percentage of
     * sqrt((actual-width)**2 + (actual-height)**2))/sqrt(2)."
     */
    (x * x + y * y).sqrt () / SQRT_2
}

#[no_mangle]
pub extern fn rsvg_length_normalize (raw_length: *const RsvgLength, draw_ctx: *const RsvgDrawingCtx) -> f64 {
    assert! (!raw_length.is_null ());

    let length: &RsvgLength = unsafe { &*raw_length };

    length.normalize (draw_ctx)
}

#[no_mangle]
pub extern fn rsvg_length_hand_normalize (raw_length: *const RsvgLength,
                                          pixels_per_inch: f64,
                                          width_or_height: f64,
                                          font_size: f64) -> f64 {
    assert! (!raw_length.is_null ());

    let length: &RsvgLength = unsafe { &*raw_length };

    length.hand_normalize (pixels_per_inch, width_or_height, font_size)
}

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

    #[test]
    fn parses_default () {
        assert_eq! (RsvgLength::parse ("42", LengthDir::Horizontal),
                    Ok (RsvgLength::new(42.0, LengthUnit::Default, LengthDir::Horizontal)));

        assert_eq! (RsvgLength::parse ("-42px", LengthDir::Horizontal),
                    Ok (RsvgLength::new(-42.0, LengthUnit::Default, LengthDir::Horizontal)));
    }

    #[test]
    fn parses_percent () {
        assert_eq! (RsvgLength::parse ("50.0%", LengthDir::Horizontal),
                    Ok (RsvgLength::new(0.5, LengthUnit::Percent, LengthDir::Horizontal)));
    }

    #[test]
    fn parses_font_em () {
        assert_eq! (RsvgLength::parse ("22.5em", LengthDir::Vertical),
                    Ok (RsvgLength::new(22.5, LengthUnit::FontEm, LengthDir::Vertical)));
    }

    #[test]
    fn parses_font_ex () {
        assert_eq! (RsvgLength::parse ("22.5ex", LengthDir::Vertical),
                    Ok (RsvgLength::new(22.5, LengthUnit::FontEx, LengthDir::Vertical)));
    }

    #[test]
    fn parses_physical_units () {
        assert_eq! (RsvgLength::parse ("72pt", LengthDir::Both),
                    Ok (RsvgLength::new(1.0, LengthUnit::Inch, LengthDir::Both)));

        assert_eq! (RsvgLength::parse ("-22.5in", LengthDir::Both),
                    Ok (RsvgLength::new(-22.5, LengthUnit::Inch, LengthDir::Both)));

        assert_eq! (RsvgLength::parse ("-254cm", LengthDir::Both),
                    Ok (RsvgLength::new(-100.0, LengthUnit::Inch, LengthDir::Both)));

        assert_eq! (RsvgLength::parse ("254mm", LengthDir::Both),
                    Ok (RsvgLength::new(10.0, LengthUnit::Inch, LengthDir::Both)));

        assert_eq! (RsvgLength::parse ("60pc", LengthDir::Both),
                    Ok (RsvgLength::new(10.0, LengthUnit::Inch, LengthDir::Both)));
    }

    #[test]
    fn parses_relative_larger () {
        assert_eq! (RsvgLength::parse ("larger", LengthDir::Vertical),
                    Ok (RsvgLength::new(0.0, LengthUnit::RelativeLarger, LengthDir::Vertical)));
    }

    #[test]
    fn parses_relative_smaller () {
        assert_eq! (RsvgLength::parse ("smaller", LengthDir::Vertical),
                    Ok (RsvgLength::new(0.0, LengthUnit::RelativeSmaller, LengthDir::Vertical)));
    }

    #[test]
    fn parses_named_sizes () {
        let names = vec![ "xx-small",
                           "x-small",
                           "small",
                           "medium",
                           "large",
                           "x-large",
                           "xx-large" ];

        let mut previous_value: Option<f64> = None;

        // Just ensure that the values are progressively larger; don't
        // enforce a particular sequence.

        for name in names {
            let length = RsvgLength::parse (name, LengthDir::Both).unwrap ();

            assert_eq! (length.unit, LengthUnit::Inch);
            assert_eq! (length.dir, LengthDir::Both);

            if let Some (v) = previous_value {
                assert! (length.length > v);
            } else {
                previous_value = Some (length.length);
            }

        }
    }

    #[test]
    fn empty_length_yields_error () {
        assert! (is_parse_error (&RsvgLength::parse ("", LengthDir::Both)));
    }

    #[test]
    fn invalid_unit_yields_error () {
        assert! (is_parse_error (&RsvgLength::parse ("8furlong", LengthDir::Both)));
    }

    #[test]
    fn invalid_font_size_yields_error () {
        // FIXME: this is intended to test the (absence of) the "larger" et al values.
        // Since they really be in FontSize, not RsvgLength, we should remember
        // to move this test to that type later.
        assert! (is_parse_error (&RsvgLength::parse ("furlong", LengthDir::Both)));
    }

    #[test]
    fn check_nonnegative_works () {
        assert! (RsvgLength::parse ("0", LengthDir::Both).and_then (|l| l.check_nonnegative ()).is_ok ());
        assert! (RsvgLength::parse ("-10", LengthDir::Both).and_then (|l| l.check_nonnegative ()).is_err ());
    }
}
<|endoftext|>


<commit_before>extern crate cairo;

#[derive(Debug)]
#[derive(PartialEq)]
pub enum Segment {
    Degenerate {            // A single lone point
        x: f64,
        y: f64
    },

    LineOrCurve {
        x1: f64, y1: f64,
        x2: f64, y2: f64,
        x3: f64, y3: f64,
        x4: f64, y4: f64
    },
}

enum SegmentState {
    Initial,
    NewSubpath,
    InSubpath,
    ClosedSubpath
}

/* This converts a cairo_path_t into a list of curveto-like segments.  Each segment can be:
 *
 * 1. Segment::Degenerate => the segment is actually a single point (x, y)
 *
 * 2. Segment::LineOrCurve => either a lineto or a curveto (or the effective lineto that results from a closepath).
 *    We have the following points:
 *       P1 = (x1, y1)
 *       P2 = (x2, y2)
 *       P3 = (x3, y3)
 *       P4 = (x4, y4)
 *
 *    The start and end points are P1 and P4, respectively.
 *    The tangent at the start point is given by the vector (P2 - P1).
 *    The tangent at the end point is given by the vector (P4 - P3).
 *    The tangents also work if the segment refers to a lineto (they will both just point in the same direction).
 */

const EPSILON: f64 = 1e-10;

fn double_equals (a: f64, b: f64) -> bool {
    (a - b).abs () < EPSILON
}

fn make_degenerate (x: f64, y: f64) -> Segment {
    Segment::Degenerate { x: x, y: y}
}

fn make_curve (x1: f64, y1: f64,
               mut x2: f64, mut y2: f64,
               mut x3: f64, mut y3: f64,
               x4: f64, y4: f64) -> Segment {
    /* Fix the tangents for when the middle control points coincide with their respective endpoints */

    if double_equals (x2, x1) && double_equals (y2, y1) {
        x2 = x3;
        y2 = y3;
    }

    if double_equals (x3, x4) && double_equals (y3, y4) {
        x3 = x2;
        y3 = y2;
    }

    Segment::LineOrCurve {
        x1: x1, y1: y1,
        x2: x2, y2: y2,
        x3: x3, y3: y3,
        x4: x4, y4: y4
    }
}

fn make_line (x1: f64, y1: f64, x2: f64, y2: f64) -> Segment {
    make_curve (x1, y1, x2, y2, x1, y1, x2, y2)
}

pub fn path_to_segments (path: cairo::Path) -> Vec<Segment> {
    let mut last_x: f64;
    let mut last_y: f64;
    let mut cur_x: f64;
    let mut cur_y: f64;
    let mut subpath_start_x: f64;
    let mut subpath_start_y: f64;
    let mut segments: Vec<Segment>;
    let mut state: SegmentState;

    cur_x = 0.0;
    cur_y = 0.0;
    subpath_start_x = 0.0;
    subpath_start_y = 0.0;

    segments = Vec::new ();
    state = SegmentState::Initial;

    for cairo_segment in path.iter () {
        last_x = cur_x;
        last_y = cur_y;

        match cairo_segment {
            cairo::PathSegment::MoveTo ((x, y)) => {
                cur_x = x;
                cur_y = y;

                subpath_start_x = cur_x;
                subpath_start_y = cur_y;

                match state {
                    SegmentState::Initial |
                    SegmentState::InSubpath => {
                        /* Ignore the very first moveto in a sequence (Initial state), or if we were
                         * already drawing within a subpath, start a new subpath.
                         */
                        state = SegmentState::NewSubpath;
                    },

                    SegmentState::NewSubpath => {
                        /* We had just begun a new subpath (i.e. from a moveto) and we got another
                         * moveto?  Output a stray point for the previous moveto.
                         */
                        segments.push (make_degenerate (last_x, last_y));
                        state = SegmentState::NewSubpath;
                    }

                    SegmentState::ClosedSubpath => {
                        /* Cairo outputs a moveto after every closepath, so that subsequent
                         * lineto/curveto commands will start at the closed vertex.
                         *
                         * We don't want to actually emit a point (a degenerate segment) in that
                         * artificial-moveto case.
                         *
                         * We'll reset to the Initial state so that a subsequent "real" moveto will
                         * be handled as the beginning of a new subpath, or a degenerate point, as
                         * usual.
                         */
                        state = SegmentState::Initial;
                    }
                }
            },

            cairo::PathSegment::LineTo ((x, y)) => {
                cur_x = x;
                cur_y = y;

                segments.push (make_line (last_x, last_y, cur_x, cur_y));

                state = SegmentState::InSubpath;
            },

            cairo::PathSegment::CurveTo ((x2, y2), (x3, y3), (x4, y4)) => {
                cur_x = x4;
                cur_y = y4;

                segments.push (make_curve (last_x, last_y, x2, y2, x3, y3, cur_x, cur_y));

                state = SegmentState::InSubpath;
            },

            cairo::PathSegment::ClosePath => {
                cur_x = subpath_start_x;
                cur_y = subpath_start_y;

                segments.push (make_line (last_x, last_y, cur_x, cur_y));

                state = SegmentState::ClosedSubpath;
            }
        }
    }

    match state {
        SegmentState::NewSubpath => {
            /* Output a lone point if we started a subpath with a
             * moveto command, but there are no subsequent commands.
             */
            segments.push (make_degenerate (cur_x, cur_y));
        },

        _ => {
        }
    }

    segments
}

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

    fn create_cr () -> cairo::Context {
        let surf = cairo::ImageSurface::create (cairo::Format::Rgb24, 1, 1);
        let cr = cairo::Context::new (&surf);

        cr
    }

    fn degenerate (x: f64, y: f64) -> Segment {
        Segment::Degenerate { x: x, y: y }
    }

    fn curve (x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> Segment {
        Segment::LineOrCurve {
            x1: x1, y1: y1, x2: x2, y2: y2, x3: x3, y3: y3, x4: x4, y4: y4
        }
    }

    fn line (x1: f64, y1: f64, x2: f64, y2: f64) -> Segment {
        curve (x1, y1, x2, y2, x1, y1, x2, y2)
    }

    fn test_path_to_segments (path: cairo::Path, expected_segments: Vec<Segment>) {
        let segments = path_to_segments (path);
        assert_eq! (expected_segments, segments);
    }

    /* Single open path; the easy case */

    fn setup_open_path () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.line_to (20.0, 10.0);
        cr.line_to (20.0, 20.0);

        cr.copy_path ()
    }

    #[test]
    fn path_to_segments_handles_open_path () {
        let expected_segments: Vec<Segment> = vec![
            line (10.0, 10.0, 20.0, 10.0),
            line (20.0, 10.0, 20.0, 20.0)
        ];

        test_path_to_segments (setup_open_path(), expected_segments);
    }

    /* Multiple open subpaths */

    fn setup_multiple_open_subpaths () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.line_to (20.0, 10.0);
        cr.line_to (20.0, 20.0);

        cr.move_to (30.0, 30.0);
        cr.line_to (40.0, 30.0);
        cr.curve_to (50.0, 35.0, 60.0, 60.0, 70.0, 70.0);
        cr.line_to (80.0, 90.0);

        cr.copy_path ()
    }

    #[test]
    fn path_to_segments_handles_multiple_open_subpaths () {
        let expected_segments: Vec<Segment> = vec![
            line  (10.0, 10.0, 20.0, 10.0),
            line  (20.0, 10.0, 20.0, 20.0),

            line  (30.0, 30.0, 40.0, 30.0),
            curve (40.0, 30.0, 50.0, 35.0, 60.0, 60.0, 70.0, 70.0),
            line  (70.0, 70.0, 80.0, 90.0)
        ];

        test_path_to_segments (setup_multiple_open_subpaths (), expected_segments);
    }

    /* Closed subpath; must have a line segment back to the first point */

    fn setup_closed_subpath () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.line_to (20.0, 10.0);
        cr.line_to (20.0, 20.0);
        cr.close_path ();

        cr.copy_path ()
    }

    #[test]
    fn path_to_segments_handles_closed_subpath () {
        let expected_segments: Vec<Segment> = vec![
            line (10.0, 10.0, 20.0, 10.0),
            line (20.0, 10.0, 20.0, 20.0),
            line (20.0, 20.0, 10.0, 10.0)
        ];

        test_path_to_segments (setup_closed_subpath (), expected_segments);
    }

    /* Multiple closed subpaths; each must have a line segment back to their
     * initial points, with no degenerate segments between subpaths.
     */

    fn setup_multiple_closed_subpaths () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.line_to (20.0, 10.0);
        cr.line_to (20.0, 20.0);
        cr.close_path ();

        cr.move_to (30.0, 30.0);
        cr.line_to (40.0, 30.0);
        cr.curve_to (50.0, 35.0, 60.0, 60.0, 70.0, 70.0);
        cr.line_to (80.0, 90.0);
        cr.close_path ();

        cr.copy_path ()
    }

    #[test]
    fn path_to_segments_handles_multiple_closed_subpaths () {
        let expected_segments: Vec<Segment> = vec![
            line  (10.0, 10.0, 20.0, 10.0),
            line  (20.0, 10.0, 20.0, 20.0),
            line  (20.0, 20.0, 10.0, 10.0),

            line  (30.0, 30.0, 40.0, 30.0),
            curve (40.0, 30.0, 50.0, 35.0, 60.0, 60.0, 70.0, 70.0),
            line  (70.0, 70.0, 80.0, 90.0),
            line  (80.0, 90.0, 30.0, 30.0)
        ];

        test_path_to_segments (setup_multiple_closed_subpaths (), expected_segments);
    }

    /* A lineto follows the first closed subpath, with no moveto to start the second subpath.  The
     * lineto must start at the first point of the first subpath.
     */

    fn setup_no_moveto_after_closepath () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.line_to (20.0, 10.0);
        cr.line_to (20.0, 20.0);
        cr.close_path ();

        cr.line_to (40.0, 30.0);

        cr.copy_path ()
    }

    #[test]
    fn path_to_segments_handles_no_moveto_after_closepath () {
        let expected_segments: Vec<Segment> = vec![
            line  (10.0, 10.0, 20.0, 10.0),
            line  (20.0, 10.0, 20.0, 20.0),
            line  (20.0, 20.0, 10.0, 10.0),

            line  (10.0, 10.0, 40.0, 30.0)
        ];

        test_path_to_segments (setup_no_moveto_after_closepath (), expected_segments);
    }

    /* Sequence of moveto; should generate degenerate points.
     *
     * This test is not enabled right now!  We create the
     * path fixtures with Cairo, and Cairo compresses
     * sequences of moveto into a single one.  So, we can't
     * really test this, as we don't get the fixture we want.
     *
     * Eventually we'll probably have to switch librsvg to
     * its own internal path representation which should
     * allow for unelided path commands, and which should
     * only build a cairo_path_t for the final rendering step.

    fn setup_sequence_of_moveto () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.move_to (20.0, 20.0);
        cr.move_to (30.0, 30.0);
        cr.move_to (40.0, 40.0);

        let path = cr.copy_path ();
        path
    }

    #[test]
    fn path_to_segments_handles_sequence_of_moveto () {
        let expected_segments: Vec<Segment> = vec! [
            degenerate (10.0, 10.0),
            degenerate (20.0, 20.0),
            degenerate (30.0, 30.0),
            degenerate (40.0, 40.0)
        ];

        test_path_to_segments (setup_sequence_of_moveto (), expected_segments);
    }
     */
}
<commit_msg>marker.rs: In tests, use the global helper functions; don't duplicate them<commit_after>extern crate cairo;

#[derive(Debug)]
#[derive(PartialEq)]
pub enum Segment {
    Degenerate {            // A single lone point
        x: f64,
        y: f64
    },

    LineOrCurve {
        x1: f64, y1: f64,
        x2: f64, y2: f64,
        x3: f64, y3: f64,
        x4: f64, y4: f64
    },
}

enum SegmentState {
    Initial,
    NewSubpath,
    InSubpath,
    ClosedSubpath
}

/* This converts a cairo_path_t into a list of curveto-like segments.  Each segment can be:
 *
 * 1. Segment::Degenerate => the segment is actually a single point (x, y)
 *
 * 2. Segment::LineOrCurve => either a lineto or a curveto (or the effective lineto that results from a closepath).
 *    We have the following points:
 *       P1 = (x1, y1)
 *       P2 = (x2, y2)
 *       P3 = (x3, y3)
 *       P4 = (x4, y4)
 *
 *    The start and end points are P1 and P4, respectively.
 *    The tangent at the start point is given by the vector (P2 - P1).
 *    The tangent at the end point is given by the vector (P4 - P3).
 *    The tangents also work if the segment refers to a lineto (they will both just point in the same direction).
 */

const EPSILON: f64 = 1e-10;

fn double_equals (a: f64, b: f64) -> bool {
    (a - b).abs () < EPSILON
}

fn make_degenerate (x: f64, y: f64) -> Segment {
    Segment::Degenerate { x: x, y: y}
}

fn make_curve (x1: f64, y1: f64,
               mut x2: f64, mut y2: f64,
               mut x3: f64, mut y3: f64,
               x4: f64, y4: f64) -> Segment {
    /* Fix the tangents for when the middle control points coincide with their respective endpoints */

    if double_equals (x2, x1) && double_equals (y2, y1) {
        x2 = x3;
        y2 = y3;
    }

    if double_equals (x3, x4) && double_equals (y3, y4) {
        x3 = x2;
        y3 = y2;
    }

    Segment::LineOrCurve {
        x1: x1, y1: y1,
        x2: x2, y2: y2,
        x3: x3, y3: y3,
        x4: x4, y4: y4
    }
}

fn make_line (x1: f64, y1: f64, x2: f64, y2: f64) -> Segment {
    make_curve (x1, y1, x2, y2, x1, y1, x2, y2)
}

pub fn path_to_segments (path: cairo::Path) -> Vec<Segment> {
    let mut last_x: f64;
    let mut last_y: f64;
    let mut cur_x: f64;
    let mut cur_y: f64;
    let mut subpath_start_x: f64;
    let mut subpath_start_y: f64;
    let mut segments: Vec<Segment>;
    let mut state: SegmentState;

    cur_x = 0.0;
    cur_y = 0.0;
    subpath_start_x = 0.0;
    subpath_start_y = 0.0;

    segments = Vec::new ();
    state = SegmentState::Initial;

    for cairo_segment in path.iter () {
        last_x = cur_x;
        last_y = cur_y;

        match cairo_segment {
            cairo::PathSegment::MoveTo ((x, y)) => {
                cur_x = x;
                cur_y = y;

                subpath_start_x = cur_x;
                subpath_start_y = cur_y;

                match state {
                    SegmentState::Initial |
                    SegmentState::InSubpath => {
                        /* Ignore the very first moveto in a sequence (Initial state), or if we were
                         * already drawing within a subpath, start a new subpath.
                         */
                        state = SegmentState::NewSubpath;
                    },

                    SegmentState::NewSubpath => {
                        /* We had just begun a new subpath (i.e. from a moveto) and we got another
                         * moveto?  Output a stray point for the previous moveto.
                         */
                        segments.push (make_degenerate (last_x, last_y));
                        state = SegmentState::NewSubpath;
                    }

                    SegmentState::ClosedSubpath => {
                        /* Cairo outputs a moveto after every closepath, so that subsequent
                         * lineto/curveto commands will start at the closed vertex.
                         *
                         * We don't want to actually emit a point (a degenerate segment) in that
                         * artificial-moveto case.
                         *
                         * We'll reset to the Initial state so that a subsequent "real" moveto will
                         * be handled as the beginning of a new subpath, or a degenerate point, as
                         * usual.
                         */
                        state = SegmentState::Initial;
                    }
                }
            },

            cairo::PathSegment::LineTo ((x, y)) => {
                cur_x = x;
                cur_y = y;

                segments.push (make_line (last_x, last_y, cur_x, cur_y));

                state = SegmentState::InSubpath;
            },

            cairo::PathSegment::CurveTo ((x2, y2), (x3, y3), (x4, y4)) => {
                cur_x = x4;
                cur_y = y4;

                segments.push (make_curve (last_x, last_y, x2, y2, x3, y3, cur_x, cur_y));

                state = SegmentState::InSubpath;
            },

            cairo::PathSegment::ClosePath => {
                cur_x = subpath_start_x;
                cur_y = subpath_start_y;

                segments.push (make_line (last_x, last_y, cur_x, cur_y));

                state = SegmentState::ClosedSubpath;
            }
        }
    }

    match state {
        SegmentState::NewSubpath => {
            /* Output a lone point if we started a subpath with a
             * moveto command, but there are no subsequent commands.
             */
            segments.push (make_degenerate (cur_x, cur_y));
        },

        _ => {
        }
    }

    segments
}

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

    fn create_cr () -> cairo::Context {
        let surf = cairo::ImageSurface::create (cairo::Format::Rgb24, 1, 1);
        let cr = cairo::Context::new (&surf);

        cr
    }

    fn degenerate (x: f64, y: f64) -> Segment {
        super::make_degenerate (x, y)
    }

    fn line (x1: f64, y1: f64, x2: f64, y2: f64) -> Segment {
        super::make_line (x1, y1, x2, y2)
    }

    fn curve (x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> Segment {
        super::make_curve (x1, y1, x2, y2, x3, y3, x4, y4)
    }

    fn test_path_to_segments (path: cairo::Path, expected_segments: Vec<Segment>) {
        let segments = path_to_segments (path);
        assert_eq! (expected_segments, segments);
    }

    /* Single open path; the easy case */

    fn setup_open_path () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.line_to (20.0, 10.0);
        cr.line_to (20.0, 20.0);

        cr.copy_path ()
    }

    #[test]
    fn path_to_segments_handles_open_path () {
        let expected_segments: Vec<Segment> = vec![
            line (10.0, 10.0, 20.0, 10.0),
            line (20.0, 10.0, 20.0, 20.0)
        ];

        test_path_to_segments (setup_open_path(), expected_segments);
    }

    /* Multiple open subpaths */

    fn setup_multiple_open_subpaths () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.line_to (20.0, 10.0);
        cr.line_to (20.0, 20.0);

        cr.move_to (30.0, 30.0);
        cr.line_to (40.0, 30.0);
        cr.curve_to (50.0, 35.0, 60.0, 60.0, 70.0, 70.0);
        cr.line_to (80.0, 90.0);

        cr.copy_path ()
    }

    #[test]
    fn path_to_segments_handles_multiple_open_subpaths () {
        let expected_segments: Vec<Segment> = vec![
            line  (10.0, 10.0, 20.0, 10.0),
            line  (20.0, 10.0, 20.0, 20.0),

            line  (30.0, 30.0, 40.0, 30.0),
            curve (40.0, 30.0, 50.0, 35.0, 60.0, 60.0, 70.0, 70.0),
            line  (70.0, 70.0, 80.0, 90.0)
        ];

        test_path_to_segments (setup_multiple_open_subpaths (), expected_segments);
    }

    /* Closed subpath; must have a line segment back to the first point */

    fn setup_closed_subpath () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.line_to (20.0, 10.0);
        cr.line_to (20.0, 20.0);
        cr.close_path ();

        cr.copy_path ()
    }

    #[test]
    fn path_to_segments_handles_closed_subpath () {
        let expected_segments: Vec<Segment> = vec![
            line (10.0, 10.0, 20.0, 10.0),
            line (20.0, 10.0, 20.0, 20.0),
            line (20.0, 20.0, 10.0, 10.0)
        ];

        test_path_to_segments (setup_closed_subpath (), expected_segments);
    }

    /* Multiple closed subpaths; each must have a line segment back to their
     * initial points, with no degenerate segments between subpaths.
     */

    fn setup_multiple_closed_subpaths () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.line_to (20.0, 10.0);
        cr.line_to (20.0, 20.0);
        cr.close_path ();

        cr.move_to (30.0, 30.0);
        cr.line_to (40.0, 30.0);
        cr.curve_to (50.0, 35.0, 60.0, 60.0, 70.0, 70.0);
        cr.line_to (80.0, 90.0);
        cr.close_path ();

        cr.copy_path ()
    }

    #[test]
    fn path_to_segments_handles_multiple_closed_subpaths () {
        let expected_segments: Vec<Segment> = vec![
            line  (10.0, 10.0, 20.0, 10.0),
            line  (20.0, 10.0, 20.0, 20.0),
            line  (20.0, 20.0, 10.0, 10.0),

            line  (30.0, 30.0, 40.0, 30.0),
            curve (40.0, 30.0, 50.0, 35.0, 60.0, 60.0, 70.0, 70.0),
            line  (70.0, 70.0, 80.0, 90.0),
            line  (80.0, 90.0, 30.0, 30.0)
        ];

        test_path_to_segments (setup_multiple_closed_subpaths (), expected_segments);
    }

    /* A lineto follows the first closed subpath, with no moveto to start the second subpath.  The
     * lineto must start at the first point of the first subpath.
     */

    fn setup_no_moveto_after_closepath () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.line_to (20.0, 10.0);
        cr.line_to (20.0, 20.0);
        cr.close_path ();

        cr.line_to (40.0, 30.0);

        cr.copy_path ()
    }

    #[test]
    fn path_to_segments_handles_no_moveto_after_closepath () {
        let expected_segments: Vec<Segment> = vec![
            line  (10.0, 10.0, 20.0, 10.0),
            line  (20.0, 10.0, 20.0, 20.0),
            line  (20.0, 20.0, 10.0, 10.0),

            line  (10.0, 10.0, 40.0, 30.0)
        ];

        test_path_to_segments (setup_no_moveto_after_closepath (), expected_segments);
    }

    /* Sequence of moveto; should generate degenerate points.
     *
     * This test is not enabled right now!  We create the
     * path fixtures with Cairo, and Cairo compresses
     * sequences of moveto into a single one.  So, we can't
     * really test this, as we don't get the fixture we want.
     *
     * Eventually we'll probably have to switch librsvg to
     * its own internal path representation which should
     * allow for unelided path commands, and which should
     * only build a cairo_path_t for the final rendering step.

    fn setup_sequence_of_moveto () -> cairo::Path {
        let cr = create_cr ();

        cr.move_to (10.0, 10.0);
        cr.move_to (20.0, 20.0);
        cr.move_to (30.0, 30.0);
        cr.move_to (40.0, 40.0);

        let path = cr.copy_path ();
        path
    }

    #[test]
    fn path_to_segments_handles_sequence_of_moveto () {
        let expected_segments: Vec<Segment> = vec! [
            degenerate (10.0, 10.0),
            degenerate (20.0, 20.0),
            degenerate (30.0, 30.0),
            degenerate (40.0, 40.0)
        ];

        test_path_to_segments (setup_sequence_of_moveto (), expected_segments);
    }
     */
}
<|endoftext|>


<commit_before>use rustc::hir::*;
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::{declare_lint_pass, declare_tool_lint};
use rustc_errors::Applicability;
use syntax::source_map::Spanned;

use crate::utils::SpanlessEq;
use crate::utils::{get_parent_expr, is_allowed, match_type, paths, span_lint, span_lint_and_sugg, walk_ptrs_ty};

declare_clippy_lint! {
    /// **What it does:** Checks for string appends of the form `x = x + y` (without
    /// `let`!).
    ///
    /// **Why is this bad?** It's not really bad, but some people think that the
    /// `.push_str(_)` method is more readable.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```rust
    /// let mut x = "Hello".to_owned();
    /// x = x + ", World";
    /// ```
    pub STRING_ADD_ASSIGN,
    pedantic,
    "using `x = x + ..` where x is a `String` instead of `push_str()`"
}

declare_clippy_lint! {
    /// **What it does:** Checks for all instances of `x + _` where `x` is of type
    /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not*
    /// match.
    ///
    /// **Why is this bad?** It's not bad in and of itself. However, this particular
    /// `Add` implementation is asymmetric (the other operand need not be `String`,
    /// but `x` does), while addition as mathematically defined is symmetric, also
    /// the `String::push_str(_)` function is a perfectly good replacement.
    /// Therefore, some dislike it and wish not to have it in their code.
    ///
    /// That said, other people think that string addition, having a long tradition
    /// in other languages is actually fine, which is why we decided to make this
    /// particular lint `allow` by default.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```rust
    /// let x = "Hello".to_owned();
    /// x + ", World"
    /// ```
    pub STRING_ADD,
    restriction,
    "using `x + ..` where x is a `String` instead of `push_str()`"
}

declare_clippy_lint! {
    /// **What it does:** Checks for the `as_bytes` method called on string literals
    /// that contain only ASCII characters.
    ///
    /// **Why is this bad?** Byte string literals (e.g., `b"foo"`) can be used
    /// instead. They are shorter but less discoverable than `as_bytes()`.
    ///
    /// **Known Problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// let bs = "a byte string".as_bytes();
    /// ```
    pub STRING_LIT_AS_BYTES,
    style,
    "calling `as_bytes` on a string literal instead of using a byte string literal"
}

declare_lint_pass!(StringAdd => [STRING_ADD, STRING_ADD_ASSIGN]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for StringAdd {
    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
        if let ExprKind::Binary(
            Spanned {
                node: BinOpKind::Add, ..
            },
            ref left,
            _,
        ) = e.node
        {
            if is_string(cx, left) {
                if !is_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) {
                    let parent = get_parent_expr(cx, e);
                    if let Some(p) = parent {
                        if let ExprKind::Assign(ref target, _) = p.node {
                            // avoid duplicate matches
                            if SpanlessEq::new(cx).eq_expr(target, left) {
                                return;
                            }
                        }
                    }
                }
                span_lint(
                    cx,
                    STRING_ADD,
                    e.span,
                    "you added something to a string. Consider using `String::push_str()` instead",
                );
            }
        } else if let ExprKind::Assign(ref target, ref src) = e.node {
            if is_string(cx, target) && is_add(cx, src, target) {
                span_lint(
                    cx,
                    STRING_ADD_ASSIGN,
                    e.span,
                    "you assigned the result of adding something to this string. Consider using \
                     `String::push_str()` instead",
                );
            }
        }
    }
}

fn is_string(cx: &LateContext<'_, '_>, e: &Expr) -> bool {
    match_type(cx, walk_ptrs_ty(cx.tables.expr_ty(e)), &paths::STRING)
}

fn is_add(cx: &LateContext<'_, '_>, src: &Expr, target: &Expr) -> bool {
    match src.node {
        ExprKind::Binary(
            Spanned {
                node: BinOpKind::Add, ..
            },
            ref left,
            _,
        ) => SpanlessEq::new(cx).eq_expr(target, left),
        ExprKind::Block(ref block, _) => {
            block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| is_add(cx, expr, target))
        },
        _ => false,
    }
}

declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for StringLitAsBytes {
    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
        use crate::utils::{in_macro_or_desugar, snippet, snippet_with_applicability};
        use syntax::ast::{LitKind, StrStyle};

        if let ExprKind::MethodCall(ref path, _, ref args) = e.node {
            if path.ident.name == sym!(as_bytes) {
                if let ExprKind::Lit(ref lit) = args[0].node {
                    if let LitKind::Str(ref lit_content, style) = lit.node {
                        let callsite = snippet(cx, args[0].span.source_callsite(), r#""foo""#);
                        let expanded = if let StrStyle::Raw(n) = style {
                            let term = (0..n).map(|_| '#').collect::<String>();
                            format!("r{0}\"{1}\"{0}", term, lit_content.as_str())
                        } else {
                            format!("\"{}\"", lit_content.as_str())
                        };
                        let mut applicability = Applicability::MachineApplicable;
                        if callsite.starts_with("include_str!") {
                            span_lint_and_sugg(
                                cx,
                                STRING_LIT_AS_BYTES,
                                e.span,
                                "calling `as_bytes()` on `include_str!(..)`",
                                "consider using `include_bytes!(..)` instead",
                                snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability).replacen(
                                    "include_str",
                                    "include_bytes",
                                    1,
                                ),
                                applicability,
                            );
                        } else if callsite == expanded
                            && lit_content.as_str().chars().all(|c| c.is_ascii())
                            && lit_content.as_str().len() <= 32
                            && !in_macro_or_desugar(args[0].span)
                        {
                            span_lint_and_sugg(
                                cx,
                                STRING_LIT_AS_BYTES,
                                e.span,
                                "calling `as_bytes()` on a string literal",
                                "consider using a byte string literal instead",
                                format!(
                                    "b{}",
                                    snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability)
                                ),
                                applicability,
                            );
                        }
                    }
                }
            }
        }
    }
}
<commit_msg>Extract semantic constant<commit_after>use rustc::hir::*;
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::{declare_lint_pass, declare_tool_lint};
use rustc_errors::Applicability;
use syntax::source_map::Spanned;

use crate::utils::SpanlessEq;
use crate::utils::{get_parent_expr, is_allowed, match_type, paths, span_lint, span_lint_and_sugg, walk_ptrs_ty};

declare_clippy_lint! {
    /// **What it does:** Checks for string appends of the form `x = x + y` (without
    /// `let`!).
    ///
    /// **Why is this bad?** It's not really bad, but some people think that the
    /// `.push_str(_)` method is more readable.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```rust
    /// let mut x = "Hello".to_owned();
    /// x = x + ", World";
    /// ```
    pub STRING_ADD_ASSIGN,
    pedantic,
    "using `x = x + ..` where x is a `String` instead of `push_str()`"
}

declare_clippy_lint! {
    /// **What it does:** Checks for all instances of `x + _` where `x` is of type
    /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not*
    /// match.
    ///
    /// **Why is this bad?** It's not bad in and of itself. However, this particular
    /// `Add` implementation is asymmetric (the other operand need not be `String`,
    /// but `x` does), while addition as mathematically defined is symmetric, also
    /// the `String::push_str(_)` function is a perfectly good replacement.
    /// Therefore, some dislike it and wish not to have it in their code.
    ///
    /// That said, other people think that string addition, having a long tradition
    /// in other languages is actually fine, which is why we decided to make this
    /// particular lint `allow` by default.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```rust
    /// let x = "Hello".to_owned();
    /// x + ", World"
    /// ```
    pub STRING_ADD,
    restriction,
    "using `x + ..` where x is a `String` instead of `push_str()`"
}

declare_clippy_lint! {
    /// **What it does:** Checks for the `as_bytes` method called on string literals
    /// that contain only ASCII characters.
    ///
    /// **Why is this bad?** Byte string literals (e.g., `b"foo"`) can be used
    /// instead. They are shorter but less discoverable than `as_bytes()`.
    ///
    /// **Known Problems:** None.
    ///
    /// **Example:**
    /// ```rust
    /// let bs = "a byte string".as_bytes();
    /// ```
    pub STRING_LIT_AS_BYTES,
    style,
    "calling `as_bytes` on a string literal instead of using a byte string literal"
}

declare_lint_pass!(StringAdd => [STRING_ADD, STRING_ADD_ASSIGN]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for StringAdd {
    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
        if let ExprKind::Binary(
            Spanned {
                node: BinOpKind::Add, ..
            },
            ref left,
            _,
        ) = e.node
        {
            if is_string(cx, left) {
                if !is_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) {
                    let parent = get_parent_expr(cx, e);
                    if let Some(p) = parent {
                        if let ExprKind::Assign(ref target, _) = p.node {
                            // avoid duplicate matches
                            if SpanlessEq::new(cx).eq_expr(target, left) {
                                return;
                            }
                        }
                    }
                }
                span_lint(
                    cx,
                    STRING_ADD,
                    e.span,
                    "you added something to a string. Consider using `String::push_str()` instead",
                );
            }
        } else if let ExprKind::Assign(ref target, ref src) = e.node {
            if is_string(cx, target) && is_add(cx, src, target) {
                span_lint(
                    cx,
                    STRING_ADD_ASSIGN,
                    e.span,
                    "you assigned the result of adding something to this string. Consider using \
                     `String::push_str()` instead",
                );
            }
        }
    }
}

fn is_string(cx: &LateContext<'_, '_>, e: &Expr) -> bool {
    match_type(cx, walk_ptrs_ty(cx.tables.expr_ty(e)), &paths::STRING)
}

fn is_add(cx: &LateContext<'_, '_>, src: &Expr, target: &Expr) -> bool {
    match src.node {
        ExprKind::Binary(
            Spanned {
                node: BinOpKind::Add, ..
            },
            ref left,
            _,
        ) => SpanlessEq::new(cx).eq_expr(target, left),
        ExprKind::Block(ref block, _) => {
            block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| is_add(cx, expr, target))
        },
        _ => false,
    }
}

// Max length a b"foo" string can take
const MAX_LENGTH_BYTE_STRING_LIT: usize = 32;

declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for StringLitAsBytes {
    fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, e: &'tcx Expr) {
        use crate::utils::{in_macro_or_desugar, snippet, snippet_with_applicability};
        use syntax::ast::{LitKind, StrStyle};

        if let ExprKind::MethodCall(ref path, _, ref args) = e.node {
            if path.ident.name == sym!(as_bytes) {
                if let ExprKind::Lit(ref lit) = args[0].node {
                    if let LitKind::Str(ref lit_content, style) = lit.node {
                        let callsite = snippet(cx, args[0].span.source_callsite(), r#""foo""#);
                        let expanded = if let StrStyle::Raw(n) = style {
                            let term = (0..n).map(|_| '#').collect::<String>();
                            format!("r{0}\"{1}\"{0}", term, lit_content.as_str())
                        } else {
                            format!("\"{}\"", lit_content.as_str())
                        };
                        let mut applicability = Applicability::MachineApplicable;
                        if callsite.starts_with("include_str!") {
                            span_lint_and_sugg(
                                cx,
                                STRING_LIT_AS_BYTES,
                                e.span,
                                "calling `as_bytes()` on `include_str!(..)`",
                                "consider using `include_bytes!(..)` instead",
                                snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability).replacen(
                                    "include_str",
                                    "include_bytes",
                                    1,
                                ),
                                applicability,
                            );
                        } else if callsite == expanded
                            && lit_content.as_str().chars().all(|c| c.is_ascii())
                            && lit_content.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT
                            && !in_macro_or_desugar(args[0].span)
                        {
                            span_lint_and_sugg(
                                cx,
                                STRING_LIT_AS_BYTES,
                                e.span,
                                "calling `as_bytes()` on a string literal",
                                "consider using a byte string literal instead",
                                format!(
                                    "b{}",
                                    snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability)
                                ),
                                applicability,
                            );
                        }
                    }
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>use super::Blob;

use std::convert::TryFrom;
use std::mem::MaybeUninit;
use std::slice::from_raw_parts_mut;

use crate::ffi;
use crate::{Error, Result};

impl<'conn> Blob<'conn> {
    /// Write `buf` to `self` starting at `write_start`, returning an error if
    /// `write_start + buf.len()` is past the end of the blob.
    ///
    /// If an error is returned, no data is written.
    ///
    /// Note: the blob cannot be resized using this function -- that must be
    /// done using SQL (for example, an `UPDATE` statement).
    ///
    /// Note: This is part of the positional I/O API, and thus takes an absolute
    /// position write to, instead of using the internal position that can be
    /// manipulated by the `std::io` traits.
    pub fn write_at(&mut self, buf: &[u8], write_start: usize) -> Result<()> {
        let len = self.len();

        if buf.len().saturating_add(write_start) > len {
            return Err(Error::BlobSizeError);
        }
        // We know `len` fits in an `i32`, so either:
        //
        // 1. `buf.len() + write_start` overflows, in which case we'd hit the
        //    return above (courtesy of `saturating_add`).
        //
        // 2. `buf.len() + write_start` doesn't overflow but is larger than len,
        //    in which case ditto.
        //
        // 3. `buf.len() + write_start` doesn't overflow but is less than len.
        //    This means that both `buf.len()` and `write_start` can also be
        //    losslessly converted to i32, since `len` came from an i32.
        // Sanity check the above.
        debug_assert!(i32::try_from(write_start).is_ok() && i32::try_from(buf.len()).is_ok());
        unsafe {
            check!(ffi::sqlite3_blob_write(
                self.blob,
                buf.as_ptr() as *const _,
                buf.len() as i32,
                write_start as i32,
            ));
        }
        Ok(())
    }

    /// Read as much as possible from `offset` to `offset + buf.len()` out of
    /// `self`, writing into `buf`. On success, returns the number of bytes
    /// written.
    ///
    /// If there's insufficient data in `self`, then the returned value will be
    /// less than `buf.len()`.
    ///
    /// See also [`Blob::raw_read_at`], which can take an uninitialized buffer,
    /// or [`Blob::read_at_exact`] which returns an error if the entire `buf` is
    /// not read.
    ///
    /// Note: This is part of the positional I/O API, and thus takes an absolute
    /// position to read from, instead of using the internal position that can
    /// be manipulated by the `std::io` traits. Consequently, it does not change
    /// that value. either.
    pub fn read_at(&self, buf: &mut [u8], read_start: usize) -> Result<usize> {
        // Safety: this is safe because `raw_read_at` never stores uninitialized
        // data into `as_uninit`.
        let as_uninit: &mut [MaybeUninit<u8>] =
            unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut _, buf.len()) };
        self.raw_read_at(as_uninit, read_start).map(|s| s.len())
    }

    /// Read as much as possible from `offset` to `offset + buf.len()` out of
    /// `self`, writing into `buf`. On success, returns the portion of `buf`
    /// which was initialized by this call.
    ///
    /// If there's insufficient data in `self`, then the returned value will be
    /// shorter than `buf`.
    ///
    /// See also [`Blob::read_at`], which takes a `&mut [u8]` buffer instead of
    /// a slice of `MaybeUninit<u8>`.
    ///
    /// Note: This is part of the positional I/O API, and thus takes an absolute
    /// position to read from, instead of using the internal position that can
    /// be manipulated by the `std::io` traits. Consequently, it does not change
    /// that value. either.
    pub fn raw_read_at<'a>(
        &self,
        buf: &'a mut [MaybeUninit<u8>],
        read_start: usize,
    ) -> Result<&'a mut [u8]> {
        let len = self.len();

        let read_len = match len.checked_sub(read_start) {
            None | Some(0) => 0,
            Some(v) => v.min(buf.len()),
        };

        if read_len == 0 {
            // We could return `Ok(&mut [])`, but it seems confusing that the
            // pointers don't match, so fabricate a empty slice of u8 with the
            // same base pointer as `buf`.
            let empty = unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, 0) };
            return Ok(empty);
        }

        // At this point we believe `read_start as i32` is lossless because:
        //
        // 1. `len as i32` is known to be lossless, since it comes from a SQLite
        //    api returning an i32.
        //
        // 2. If we got here, `len.checked_sub(read_start)` was Some (or else
        //    we'd have hit the `if read_len == 0` early return), so `len` must
        //    be larger than `read_start`, and so it must fit in i32 as well.
        debug_assert!(i32::try_from(read_start).is_ok());

        // We also believe that `read_start + read_len <= len` because:
        //
        // 1. This is equivalent to `read_len <= len - read_start` via algebra.
        // 2. We know that `read_len` is `min(len - read_start, buf.len())`
        // 3. Expanding, this is `min(len - read_start, buf.len()) <= len - read_start`,
        //    or `min(A, B) <= A` which is clearly true.
        //
        // Note that this stuff is in debug_assert so no need to use checked_add
        // and such -- we'll always panic on overflow in debug builds.
        debug_assert!(read_start + read_len <= len);

        // These follow naturally.
        debug_assert!(
            buf.len() <= read_len
                && i32::try_from(buf.len()).is_ok()
                && i32::try_from(read_len).is_ok()
        );

        unsafe {
            check!(ffi::sqlite3_blob_read(
                self.blob,
                buf.as_mut_ptr() as *mut _,
                read_len as i32,
                read_start as i32,
            ));

            Ok(from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, read_len))
        }
    }

    /// Equivalent to [`Blob::read_at`], but returns a `BlobSizeError` if `buf`
    /// is not fully initialized.
    pub fn read_at_exact(&self, buf: &mut [u8], read_start: usize) -> Result<()> {
        let n = self.read_at(buf, read_start)?;
        if n != buf.len() {
            Err(Error::BlobSizeError)
        } else {
            Ok(())
        }
    }

    /// Equivalent to [`Blob::raw_read_at`], but returns a `BlobSizeError` if
    /// `buf` is not fully initialized.
    pub fn raw_read_at_exact<'a>(
        &self,
        buf: &'a mut [MaybeUninit<u8>],
        read_start: usize,
    ) -> Result<&'a mut [u8]> {
        let buflen = buf.len();
        let initted = self.raw_read_at(buf, read_start)?;
        if initted.len() != buflen {
            Err(Error::BlobSizeError)
        } else {
            Ok(initted)
        }
    }
}
<commit_msg>Add test coverage for positional blob io<commit_after>use super::Blob;

use std::convert::TryFrom;
use std::mem::MaybeUninit;
use std::slice::from_raw_parts_mut;

use crate::ffi;
use crate::{Error, Result};

impl<'conn> Blob<'conn> {
    /// Write `buf` to `self` starting at `write_start`, returning an error if
    /// `write_start + buf.len()` is past the end of the blob.
    ///
    /// If an error is returned, no data is written.
    ///
    /// Note: the blob cannot be resized using this function -- that must be
    /// done using SQL (for example, an `UPDATE` statement).
    ///
    /// Note: This is part of the positional I/O API, and thus takes an absolute
    /// position write to, instead of using the internal position that can be
    /// manipulated by the `std::io` traits.
    pub fn write_at(&mut self, buf: &[u8], write_start: usize) -> Result<()> {
        let len = self.len();

        if buf.len().saturating_add(write_start) > len {
            return Err(Error::BlobSizeError);
        }
        // We know `len` fits in an `i32`, so either:
        //
        // 1. `buf.len() + write_start` overflows, in which case we'd hit the
        //    return above (courtesy of `saturating_add`).
        //
        // 2. `buf.len() + write_start` doesn't overflow but is larger than len,
        //    in which case ditto.
        //
        // 3. `buf.len() + write_start` doesn't overflow but is less than len.
        //    This means that both `buf.len()` and `write_start` can also be
        //    losslessly converted to i32, since `len` came from an i32.
        // Sanity check the above.
        debug_assert!(i32::try_from(write_start).is_ok() && i32::try_from(buf.len()).is_ok());
        unsafe {
            check!(ffi::sqlite3_blob_write(
                self.blob,
                buf.as_ptr() as *const _,
                buf.len() as i32,
                write_start as i32,
            ));
        }
        Ok(())
    }

    /// Read as much as possible from `offset` to `offset + buf.len()` out of
    /// `self`, writing into `buf`. On success, returns the number of bytes
    /// written.
    ///
    /// If there's insufficient data in `self`, then the returned value will be
    /// less than `buf.len()`.
    ///
    /// See also [`Blob::raw_read_at`], which can take an uninitialized buffer,
    /// or [`Blob::read_at_exact`] which returns an error if the entire `buf` is
    /// not read.
    ///
    /// Note: This is part of the positional I/O API, and thus takes an absolute
    /// position to read from, instead of using the internal position that can
    /// be manipulated by the `std::io` traits. Consequently, it does not change
    /// that value. either.
    pub fn read_at(&self, buf: &mut [u8], read_start: usize) -> Result<usize> {
        // Safety: this is safe because `raw_read_at` never stores uninitialized
        // data into `as_uninit`.
        let as_uninit: &mut [MaybeUninit<u8>] =
            unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut _, buf.len()) };
        self.raw_read_at(as_uninit, read_start).map(|s| s.len())
    }

    /// Read as much as possible from `offset` to `offset + buf.len()` out of
    /// `self`, writing into `buf`. On success, returns the portion of `buf`
    /// which was initialized by this call.
    ///
    /// If there's insufficient data in `self`, then the returned value will be
    /// shorter than `buf`.
    ///
    /// See also [`Blob::read_at`], which takes a `&mut [u8]` buffer instead of
    /// a slice of `MaybeUninit<u8>`.
    ///
    /// Note: This is part of the positional I/O API, and thus takes an absolute
    /// position to read from, instead of using the internal position that can
    /// be manipulated by the `std::io` traits. Consequently, it does not change
    /// that value. either.
    pub fn raw_read_at<'a>(
        &self,
        buf: &'a mut [MaybeUninit<u8>],
        read_start: usize,
    ) -> Result<&'a mut [u8]> {
        let len = self.len();

        let read_len = match len.checked_sub(read_start) {
            None | Some(0) => 0,
            Some(v) => v.min(buf.len()),
        };

        if read_len == 0 {
            // We could return `Ok(&mut [])`, but it seems confusing that the
            // pointers don't match, so fabricate a empty slice of u8 with the
            // same base pointer as `buf`.
            let empty = unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, 0) };
            return Ok(empty);
        }

        // At this point we believe `read_start as i32` is lossless because:
        //
        // 1. `len as i32` is known to be lossless, since it comes from a SQLite
        //    api returning an i32.
        //
        // 2. If we got here, `len.checked_sub(read_start)` was Some (or else
        //    we'd have hit the `if read_len == 0` early return), so `len` must
        //    be larger than `read_start`, and so it must fit in i32 as well.
        debug_assert!(i32::try_from(read_start).is_ok());

        // We also believe that `read_start + read_len <= len` because:
        //
        // 1. This is equivalent to `read_len <= len - read_start` via algebra.
        // 2. We know that `read_len` is `min(len - read_start, buf.len())`
        // 3. Expanding, this is `min(len - read_start, buf.len()) <= len - read_start`,
        //    or `min(A, B) <= A` which is clearly true.
        //
        // Note that this stuff is in debug_assert so no need to use checked_add
        // and such -- we'll always panic on overflow in debug builds.
        debug_assert!(read_start + read_len <= len);

        // These follow naturally.
        debug_assert!(buf.len() >= read_len);
        debug_assert!(i32::try_from(buf.len()).is_ok());
        debug_assert!(i32::try_from(read_len).is_ok());

        unsafe {
            check!(ffi::sqlite3_blob_read(
                self.blob,
                buf.as_mut_ptr() as *mut _,
                read_len as i32,
                read_start as i32,
            ));

            Ok(from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, read_len))
        }
    }

    /// Equivalent to [`Blob::read_at`], but returns a `BlobSizeError` if `buf`
    /// is not fully initialized.
    pub fn read_at_exact(&self, buf: &mut [u8], read_start: usize) -> Result<()> {
        let n = self.read_at(buf, read_start)?;
        if n != buf.len() {
            Err(Error::BlobSizeError)
        } else {
            Ok(())
        }
    }

    /// Equivalent to [`Blob::raw_read_at`], but returns a `BlobSizeError` if
    /// `buf` is not fully initialized.
    pub fn raw_read_at_exact<'a>(
        &self,
        buf: &'a mut [MaybeUninit<u8>],
        read_start: usize,
    ) -> Result<&'a mut [u8]> {
        let buflen = buf.len();
        let initted = self.raw_read_at(buf, read_start)?;
        if initted.len() != buflen {
            Err(Error::BlobSizeError)
        } else {
            Ok(initted)
        }
    }
}

#[cfg(test)]
mod test {
    use crate::{Connection, DatabaseName, NO_PARAMS};
    // to ensure we don't modify seek pos
    use std::io::Seek as _;

    #[test]
    fn test_pos_io() {
        let db = Connection::open_in_memory().unwrap();
        db.execute_batch("CREATE TABLE test_table(content BLOB);")
            .unwrap();
        db.execute(
            "INSERT INTO test_table(content) VALUES (ZEROBLOB(10))",
            NO_PARAMS,
        )
        .unwrap();

        let rowid = db.last_insert_rowid();
        let mut blob = db
            .blob_open(DatabaseName::Main, "test_table", "content", rowid, false)
            .unwrap();
        // modify the seek pos to ensure we aren't using it or modifying it.
        blob.seek(std::io::SeekFrom::Start(1)).unwrap();

        let one2ten: [u8; 10] = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10];
        blob.write_at(&one2ten, 0).unwrap();

        let mut s = [0u8; 10];
        blob.read_at_exact(&mut s, 0).unwrap();
        assert_eq!(&s, &one2ten, "write should go through");
        assert!(blob.read_at_exact(&mut s, 1).is_err());

        blob.read_at_exact(&mut s, 0).unwrap();
        assert_eq!(&s, &one2ten, "should be unchanged");

        let mut fives = [0u8; 5];
        blob.read_at_exact(&mut fives, 0).unwrap();
        assert_eq!(&fives, &[1u8, 2, 3, 4, 5]);

        blob.read_at_exact(&mut fives, 5).unwrap();
        assert_eq!(&fives, &[6u8, 7, 8, 9, 10]);
        assert!(blob.read_at_exact(&mut fives, 7).is_err());
        assert!(blob.read_at_exact(&mut fives, 12).is_err());
        assert!(blob.read_at_exact(&mut fives, 10).is_err());
        assert!(blob.read_at_exact(&mut fives, i32::MAX as usize).is_err());
        assert!(blob
            .read_at_exact(&mut fives, i32::MAX as usize + 1)
            .is_err());

        // zero length writes are fine if in bounds
        blob.read_at_exact(&mut [], 10).unwrap();
        blob.read_at_exact(&mut [], 0).unwrap();
        blob.read_at_exact(&mut [], 5).unwrap();

        blob.write_at(&[16, 17, 18, 19, 20], 5).unwrap();
        blob.read_at_exact(&mut s, 0).unwrap();
        assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]);

        assert!(blob.write_at(&[100, 99, 98, 97, 96], 6).is_err());
        assert!(blob
            .write_at(&[100, 99, 98, 97, 96], i32::MAX as usize)
            .is_err());
        assert!(blob
            .write_at(&[100, 99, 98, 97, 96], i32::MAX as usize + 1)
            .is_err());

        blob.read_at_exact(&mut s, 0).unwrap();
        assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]);

        let end_pos = blob.seek(std::io::SeekFrom::Current(0)).unwrap();
        assert_eq!(end_pos, 1);
    }
}
<|endoftext|>


<commit_before>use std::collections::BTreeMap;
use super::traits::Search;

pub struct BoyerMoore <'a> {
    needle: &'a [u8],
    bad_chars: BTreeMap<u8, usize>,
}

impl <'a> BoyerMoore <'a> {

    pub fn new(needle: &'a [u8]) -> BoyerMoore {
        BoyerMoore {
            needle: needle,
            bad_chars: build_bad_chars_map(&needle),
        }
    }
}

fn build_bad_chars_map(needle: &[u8]) -> BTreeMap<u8, usize> {
    let mut map = BTreeMap::new();
    let mut n = needle.len() - 1;
    while n != 0 {
        n -= 1;
        let c = needle[n];
        if !map.contains_key(&c) {
            map.insert(c, needle.len() - n - 1);
        }
    }
    map
}

impl <'a> Search<'a> for BoyerMoore <'a> {

    fn first_index_of(&self, haystack: &'a [u8]) -> Option<usize> {
        let mut i = self.needle.len() - 1;
        'outer: while i < haystack.len() {
            let mut j = self.needle.len();
            let mut k = i + 1;
            while j != 0 {
                j -= 1;
                k -= 1;
                if self.needle[j] != haystack[k] {
                    i += *self.bad_chars.get(&haystack[k]).unwrap_or(&self.needle.len());
                    continue 'outer;
                }
            }
            return Some(i - self.needle.len() + 1);
        }
        None
    }
}

#[cfg(test)]
pub mod test {

    use super::BoyerMoore;
    use traits::Search;

    #[test]
    pub fn test_simple() {
        let needle = BoyerMoore::new(&"ghi".as_bytes());
        let haystack = "abc def ghi jkl".as_bytes();
        assert_eq!(Some(8), needle.first_index_of(&haystack));
    }

    #[test]
    pub fn test_bad_char() {
        let needle = BoyerMoore::new(&"abacac".as_bytes());
        let haystack = "acacacababadabacacad".as_bytes();
        assert_eq!(Some(12), needle.first_index_of(&haystack));
    }
}<commit_msg>Fixed a bug with repeated characters in bad char map. Improved perf slightly<commit_after>use super::traits::Search;

pub struct BoyerMoore <'a> {
    needle: &'a [u8],
    bad_chars: [usize; 256],
}

impl <'a> BoyerMoore <'a> {

    pub fn new(needle: &'a [u8]) -> BoyerMoore {
        BoyerMoore {
            needle: needle,
            bad_chars: build_bad_chars_map(&needle),
        }
    }

    #[inline]
    fn get_bad_char_offset(&self, bad_char: u8) -> usize {
        self.bad_chars[bad_char as usize]
    }
}

fn build_bad_chars_map(needle: &[u8]) -> [usize; 256] {
    let mut map = [needle.len(); 256];
    for i in 0 .. needle.len() - 1 {
        let c = needle[i] as usize;
        map[c] = needle.len() - i - 1;
    }
    map
}

impl <'a> Search<'a> for BoyerMoore <'a> {

    fn first_index_of(&self, haystack: &'a [u8]) -> Option<usize> {
        let mut position = 0;
        let max_position = haystack.len() - self.needle.len();
        while position <= max_position {
            let mut needle_position = self.needle.len() - 1;
            while haystack[position + needle_position] == self.needle[needle_position] {
                if needle_position == 0 {
                    return Some(position);
                } else {
                    needle_position -= 1;
                }
            }
            let bad_char = haystack[position + self.needle.len() - 1];
            position += self.get_bad_char_offset(bad_char);
        }
        None
    }
}

#[cfg(test)]
pub mod test {

    use super::BoyerMoore;
    use traits::Search;

    #[test]
    pub fn test_simple() {
        let needle = BoyerMoore::new(&"ghi".as_bytes());
        let haystack = "abc def ghi jkl".as_bytes();
        assert_eq!(Some(8), needle.first_index_of(&haystack));
    }

    #[test]
    pub fn test_bad_char() {
        let needle = BoyerMoore::new(&"abacac".as_bytes());
        let haystack = "acacacababadabacacad".as_bytes();
        assert_eq!(Some(12), needle.first_index_of(&haystack));
    }

    #[test]
    pub fn test_bad_char2() {
        let needle = BoyerMoore::new(&"abacab".as_bytes());
        let haystack = "acacacababadabacabad".as_bytes();
        assert_eq!(Some(12), needle.first_index_of(&haystack));
    }
}
<|endoftext|>


<commit_before>use clap::ArgMatches;

use config::Config;
use doapi::{DoManager, DoRequest};
use message::CliMessage;

pub fn run(m: &ArgMatches, cfg: &mut Config) {
    if m.is_present("verbose") { cfg.verbose = true; }
    if m.is_present("nosend") { cfg.no_send = true; }
    let domgr = DoManager::with_token(&cfg.auth[..]);
    if cfg.verbose || m.is_present("verbose") { CliMessage::Token(&cfg.auth[..]).display(); }
    match m.subcommand() {
        ("actions", Some(m)) => {
            if cfg.verbose {
                CliMessage::Request(
                    &domgr.account()
                         .actions()
                         .to_string()
                         .replace("\n", "\n\t")[..]).display();
            }
            if cfg.no_send || m.is_present("nosend") { return }
            if cfg.verbose || m.is_present("verbose") {
                CliMessage::JsonResponse.display();
                match domgr.account().actions().retrieve_json() {
                    Ok(s)  => {
                        CliMessage::Success.display();
                        println!("\n\t{}\n", s);
                    },
                    Err(e) => {
                        CliMessage::Failure.display();
                        println!("\n\t{}\n", e);
                    }
                }
            }
            CliMessage::Actions.display();
            match domgr.account().actions().retrieve() {
                Ok(v) => {
                    CliMessage::Success.display();
                    for act in v.iter() {
                        CliMessage::Action.display();
                        println!("\t{}\n", &act.to_string()[..].replace("\n", "\n\t"));
                    }
                    if v.is_empty() { println!("\tNo actions to dipslay"); }
                },
                Err(e) => {
                    CliMessage::Failure.display();
                    println!("{}\n", e);
                }
            }
        },
        ("action", Some(m)) => {
            let id = m.value_of("id").unwrap();
            if cfg.verbose || m.is_present("verbose") {
                CliMessage::Request(
                    &domgr.account()
                         .action(id)
                         .to_string()
                         .replace("\n", "\n\t")[..]).display();
            }
            if cfg.no_send || m.is_present("nosend") { return }
            if cfg.verbose || m.is_present("verbose") {
                CliMessage::JsonResponse.display();
                match domgr.account().action(id).retrieve_json() {
                    Ok(s) => {
                        CliMessage::Success.display();
                        println!("\n\t{}\n", s);
                    },
                    Err(e) => {
                        CliMessage::Failure.display();
                        println!("\n\t{}\n", e);
                    }
                }
            }
            CliMessage::ActionId(id).display();
            match domgr.account().action(id).retrieve() {
                Ok(s) => {
                    CliMessage::Success.display();
                    println!("\n\t{}\n", s);
                },
                Err(e) => {
                    CliMessage::Failure.display();
                    println!("\n\t{}\n", e);
                }
            }
        },
        _ => {
            // No subcommand
            if cfg.verbose {
                CliMessage::Request(
                    &domgr.account()
                         .to_string()
                         .replace("\n", "\n\t")[..]).display();
            }
            if cfg.no_send { return }
            if cfg.verbose {
                CliMessage::JsonResponse.display();
                match domgr.account().retrieve_json() {
                    Ok(s) => {
                        CliMessage::Success.display();
                        println!("\n\t{}\n", s);
                    },
                    Err(e) => {
                        CliMessage::Failure.display();
                        println!("\n\t{}\n", e);
                    }
                }
            }
            CliMessage::Account.display();
            match domgr.account().retrieve() {
                Ok(s) => {
                    CliMessage::Success.display();
                    println!("\n\t{}\n", &s.to_string()[..].replace("\n", "\n\t"));
                    match domgr.account().retrieve_header() {
                        Ok(h)  => println!("\t{}\n",&h.to_string()[..].replace("\n", "\n\t")),
                        Err(_) => println!("\tUnable to retrieve response header information\n"),
                    }
                },
                Err(e) => {
                    CliMessage::Failure.display();
                    println!("\n\t{}\n", e);
                }
            }
        }
    }
}
<commit_msg>imp: improves output of account action <id> command<commit_after>use clap::ArgMatches;

use config::Config;
use doapi::{DoManager, DoRequest};
use message::CliMessage;

pub fn run(m: &ArgMatches, cfg: &mut Config) {
    if m.is_present("verbose") { cfg.verbose = true; }
    if m.is_present("nosend") { cfg.no_send = true; }
    let domgr = DoManager::with_token(&cfg.auth[..]);
    if cfg.verbose || m.is_present("verbose") { CliMessage::Token(&cfg.auth[..]).display(); }
    match m.subcommand() {
        ("actions", Some(m)) => {
            if cfg.verbose {
                CliMessage::Request(
                    &domgr.account()
                         .actions()
                         .to_string()
                         .replace("\n", "\n\t")[..]).display();
            }
            if cfg.no_send || m.is_present("nosend") { return }
            if cfg.verbose || m.is_present("verbose") {
                CliMessage::JsonResponse.display();
                match domgr.account().actions().retrieve_json() {
                    Ok(s)  => {
                        CliMessage::Success.display();
                        println!("\n\t{}\n", s);
                    },
                    Err(e) => {
                        CliMessage::Failure.display();
                        println!("\n\t{}\n", e);
                    }
                }
            }
            CliMessage::Actions.display();
            match domgr.account().actions().retrieve() {
                Ok(v) => {
                    CliMessage::Success.display();
                    for act in v.iter() {
                        CliMessage::Action.display();
                        println!("\t{}\n", &act.to_string()[..].replace("\n", "\n\t"));
                    }
                    if v.is_empty() { println!("\tNo actions to dipslay"); }
                },
                Err(e) => {
                    CliMessage::Failure.display();
                    println!("{}\n", e);
                }
            }
        },
        ("action", Some(m)) => {
            let id = m.value_of("id").unwrap();
            if cfg.verbose || m.is_present("verbose") {
                CliMessage::Request(
                    &domgr.account()
                         .action(id)
                         .to_string()
                         .replace("\n", "\n\t")[..]).display();
            }
            if cfg.no_send || m.is_present("nosend") { return }
            if cfg.verbose || m.is_present("verbose") {
                CliMessage::JsonResponse.display();
                match domgr.account().action(id).retrieve_json() {
                    Ok(s) => {
                        CliMessage::Success.display();
                        println!("\n\t{}\n", s);
                    },
                    Err(e) => {
                        CliMessage::Failure.display();
                        println!("\n\t{}\n", e);
                    }
                }
            }
            CliMessage::ActionId(id).display();
            match domgr.account().action(id).retrieve() {
                Ok(s) => {
                    CliMessage::Success.display();
                    println!("\n\t{}\n", &s.to_string()[..].replace("\n", "\n\t"));
                },
                Err(e) => {
                    CliMessage::Failure.display();
                    println!("\n\t{}\n", e);
                }
            }
        },
        _ => {
            // No subcommand
            if cfg.verbose {
                CliMessage::Request(
                    &domgr.account()
                         .to_string()
                         .replace("\n", "\n\t")[..]).display();
            }
            if cfg.no_send { return }
            if cfg.verbose {
                CliMessage::JsonResponse.display();
                match domgr.account().retrieve_json() {
                    Ok(s) => {
                        CliMessage::Success.display();
                        println!("\n\t{}\n", s);
                    },
                    Err(e) => {
                        CliMessage::Failure.display();
                        println!("\n\t{}\n", e);
                    }
                }
            }
            CliMessage::Account.display();
            match domgr.account().retrieve() {
                Ok(s) => {
                    CliMessage::Success.display();
                    println!("\n\t{}\n", &s.to_string()[..].replace("\n", "\n\t"));
                    match domgr.account().retrieve_header() {
                        Ok(h)  => println!("\t{}\n",&h.to_string()[..].replace("\n", "\n\t")),
                        Err(_) => println!("\tUnable to retrieve response header information\n"),
                    }
                },
                Err(e) => {
                    CliMessage::Failure.display();
                    println!("\n\t{}\n", e);
                }
            }
        }
    }
}
<|endoftext|>