<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

// SVG 1.1 (Second Edition)
// https://www.w3.org/TR/SVG/
<% data.new_style_struct("InheritedSVG",
                         inherited=True,
                         gecko_name="SVG") %>

// TODO(emilio): Should some of these types be animatable?

// Section 10 - Text

${helpers.single_keyword("text-anchor",
                         "start middle end",
                         products="gecko",
                         animatable=False,
                         spec="https://www.w3.org/TR/SVG/text.html#TextAnchorProperty")}

// Section 11 - Painting: Filling, Stroking and Marker Symbols
${helpers.single_keyword("color-interpolation",
                         "auto sRGB linearRGB",
                         products="gecko",
                         animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationProperty")}

${helpers.single_keyword("color-interpolation-filters", "auto sRGB linearRGB",
                         products="gecko",
                         gecko_constant_prefix="NS_STYLE_COLOR_INTERPOLATION",
                         animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationFiltersProperty")}

${helpers.predefined_type(
    "fill", "SVGPaint",
    "::values::computed::SVGPaint::black()",
    products="gecko",
    animatable=False,
    boxed=True,
    spec="https://www.w3.org/TR/SVG2/painting.html#SpecifyingFillPaint")}

${helpers.predefined_type("fill-opacity", "Opacity", "1.0",
                          products="gecko", animatable=False,
                          spec="https://www.w3.org/TR/SVG11/painting.html#FillOpacityProperty")}

${helpers.single_keyword("fill-rule", "nonzero evenodd",
                         gecko_enum_prefix="StyleFillRule",
                         gecko_inexhaustive=True,
                         products="gecko", animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#FillRuleProperty")}

${helpers.single_keyword("shape-rendering",
                         "auto optimizeSpeed crispEdges geometricPrecision",
                         products="gecko",
                         animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#ShapeRenderingProperty")}

${helpers.predefined_type(
    "stroke", "SVGPaint",
    "Default::default()",
    products="gecko",
    animatable=False,
    boxed=True,
    spec="https://www.w3.org/TR/SVG2/painting.html#SpecifyingStrokePaint")}

${helpers.predefined_type(
    "stroke-width", "LengthOrPercentage",
    "computed::LengthOrPercentage::one()",
    "parse_numbers_are_pixels_non_negative",
    products="gecko",
    animatable=True,
    needs_context=False,
    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeWidth")}

${helpers.single_keyword("stroke-linecap", "butt round square",
                         products="gecko", animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty")}

${helpers.single_keyword("stroke-linejoin", "miter round bevel",
                         products="gecko", animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty")}

${helpers.predefined_type("stroke-miterlimit", "Number", "4.0",
                          "parse_at_least_one", products="gecko",
                          needs_context=False,
                          animatable=False,
                          spec="https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty")}

${helpers.predefined_type("stroke-opacity", "Opacity", "1.0",
                          products="gecko", animatable=False,
                          spec="https://www.w3.org/TR/SVG11/painting.html#StrokeOpacityProperty")}

${helpers.predefined_type("stroke-dasharray", "LoPOrNumber", "Either::Second(0.0)",
                          "parse_non_negative",
                          vector="True",
                          products="gecko",
                          animatable="False",
                          space_separated_allowed="True",
                          spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing")}

${helpers.predefined_type(
    "stroke-dashoffset", "LengthOrPercentage",
    "computed::LengthOrPercentage::zero()",
    "parse_numbers_are_pixels",
    products="gecko",
    animatable=True,
    needs_context=False,
    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing")}

// Section 14 - Clipping, Masking and Compositing
${helpers.single_keyword("clip-rule", "nonzero evenodd",
                         products="gecko",
                         gecko_enum_prefix="StyleFillRule",
                         gecko_inexhaustive=True,
                         animatable=False,
                         spec="https://www.w3.org/TR/SVG11/masking.html#ClipRuleProperty")}

${helpers.predefined_type("marker-start", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          animatable="False",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

${helpers.predefined_type("marker-mid", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          animatable="False",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

${helpers.predefined_type("marker-end", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          animatable="False",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

<%helpers:longhand name="paint-order"
                   animatable="False"
                   products="gecko"
                   spec="https://www.w3.org/TR/SVG2/painting.html#PaintOrder">

    use values::computed::ComputedValueAsSpecified;
    use std::fmt;
    use style_traits::ToCss;
    use values::HasViewportPercentage;

    pub const NORMAL: u8 = 0;
    pub const FILL: u8 = 1;
    pub const STROKE: u8 = 2;
    pub const MARKERS: u8 = 3;

    // number of bits for each component
    pub const SHIFT: u8 = 2;
    // mask with above bits set
    pub const MASK: u8 = 0b11;
    // number of non-normal keyword values
    pub const COUNT: u8 = 3;
    // all keywords
    pub const ALL: [u8; 3] = [FILL, STROKE, MARKERS];

    /// Represented as a six-bit field, of 3 two-bit pairs
    ///
    /// Each pair can be set to FILL, STROKE, or MARKERS
    /// Lowest significant bit pairs are highest priority.
    ///  `normal` is the empty bitfield. The three pairs are
    /// never zero in any case other than `normal`.
    ///
    /// Higher priority values, i.e. the values specified first,
    /// will be painted first (and may be covered by paintings of lower priority)
    #[derive(PartialEq, Clone, Copy, Debug)]
    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
    pub struct SpecifiedValue(pub u8);

    pub mod computed_value {
        pub use super::SpecifiedValue as T;
    }

    pub fn get_initial_value() -> SpecifiedValue {
      SpecifiedValue(NORMAL)
    }

    impl SpecifiedValue {
        pub fn bits_at(&self, pos: u8) -> u8 {
            (self.0 >> pos * SHIFT) & MASK
        }
    }

    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
        if let Ok(()) = input.try(|i| i.expect_ident_matching("normal")) {
            Ok(SpecifiedValue(0))
        } else {
            let mut value = 0;
            // bitfield representing what we've seen so far
            // bit 1 is fill, bit 2 is stroke, bit 3 is markers
            let mut seen = 0;
            let mut pos = 0;

            loop {

                let result = input.try(|i| {
                    match_ignore_ascii_case! { i.expect_ident()?,
                        "fill" => Ok(FILL),
                        "stroke" => Ok(STROKE),
                        "markers" => Ok(MARKERS),
                        _ => Err(())
                    }
                });

                match result {
                    Ok(val) => {
                        if (seen & (1 << val)) != 0 {
                            // don't parse the same ident twice
                            return Err(())
                        } else {
                            value |= val << (pos * SHIFT);
                            seen |= 1 << val;
                            pos += 1;
                        }
                    }
                    Err(()) => break,
                }
            }

            if value == 0 {
                // couldn't find any keyword
                Err(())
            } else {
                // fill in rest
                for i in pos..COUNT {
                    for paint in &ALL {
                        // if not seen, set bit at position, mark as seen
                        if (seen & (1 << paint)) == 0 {
                            seen |= 1 << paint;
                            value |= paint << (i * SHIFT);
                            break;
                        }
                    }
                }

                Ok(SpecifiedValue(value))
            }
        }
    }

    impl ToCss for SpecifiedValue {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            if self.0 == 0 {
                return dest.write_str("normal")
            }

            for pos in 0..COUNT {
                if pos != 0 {
                    dest.write_str(" ")?
                }
                match self.bits_at(pos) {
                    FILL => dest.write_str("fill")?,
                    STROKE => dest.write_str("stroke")?,
                    MARKERS => dest.write_str("markers")?,
                    _ => unreachable!(),
                }
            }
            Ok(())
        }
    }

    no_viewport_percentage!(SpecifiedValue);

    impl ComputedValueAsSpecified for SpecifiedValue { }
</%helpers:longhand>

<commit_msg>Define keyword values in ASCII lowercase<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

// SVG 1.1 (Second Edition)
// https://www.w3.org/TR/SVG/
<% data.new_style_struct("InheritedSVG",
                         inherited=True,
                         gecko_name="SVG") %>

// TODO(emilio): Should some of these types be animatable?

// Section 10 - Text

${helpers.single_keyword("text-anchor",
                         "start middle end",
                         products="gecko",
                         animatable=False,
                         spec="https://www.w3.org/TR/SVG/text.html#TextAnchorProperty")}

// Section 11 - Painting: Filling, Stroking and Marker Symbols
${helpers.single_keyword("color-interpolation",
                         "auto srgb linearrgb",
                         products="gecko",
                         animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationProperty")}

${helpers.single_keyword("color-interpolation-filters", "auto srgb linearrgb",
                         products="gecko",
                         gecko_constant_prefix="NS_STYLE_COLOR_INTERPOLATION",
                         animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationFiltersProperty")}

${helpers.predefined_type(
    "fill", "SVGPaint",
    "::values::computed::SVGPaint::black()",
    products="gecko",
    animatable=False,
    boxed=True,
    spec="https://www.w3.org/TR/SVG2/painting.html#SpecifyingFillPaint")}

${helpers.predefined_type("fill-opacity", "Opacity", "1.0",
                          products="gecko", animatable=False,
                          spec="https://www.w3.org/TR/SVG11/painting.html#FillOpacityProperty")}

${helpers.single_keyword("fill-rule", "nonzero evenodd",
                         gecko_enum_prefix="StyleFillRule",
                         gecko_inexhaustive=True,
                         products="gecko", animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#FillRuleProperty")}

${helpers.single_keyword("shape-rendering",
                         "auto optimizespeed crispedges geometricprecision",
                         products="gecko",
                         animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#ShapeRenderingProperty")}

${helpers.predefined_type(
    "stroke", "SVGPaint",
    "Default::default()",
    products="gecko",
    animatable=False,
    boxed=True,
    spec="https://www.w3.org/TR/SVG2/painting.html#SpecifyingStrokePaint")}

${helpers.predefined_type(
    "stroke-width", "LengthOrPercentage",
    "computed::LengthOrPercentage::one()",
    "parse_numbers_are_pixels_non_negative",
    products="gecko",
    animatable=True,
    needs_context=False,
    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeWidth")}

${helpers.single_keyword("stroke-linecap", "butt round square",
                         products="gecko", animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty")}

${helpers.single_keyword("stroke-linejoin", "miter round bevel",
                         products="gecko", animatable=False,
                         spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty")}

${helpers.predefined_type("stroke-miterlimit", "Number", "4.0",
                          "parse_at_least_one", products="gecko",
                          needs_context=False,
                          animatable=False,
                          spec="https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty")}

${helpers.predefined_type("stroke-opacity", "Opacity", "1.0",
                          products="gecko", animatable=False,
                          spec="https://www.w3.org/TR/SVG11/painting.html#StrokeOpacityProperty")}

${helpers.predefined_type("stroke-dasharray", "LoPOrNumber", "Either::Second(0.0)",
                          "parse_non_negative",
                          vector="True",
                          products="gecko",
                          animatable="False",
                          space_separated_allowed="True",
                          spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing")}

${helpers.predefined_type(
    "stroke-dashoffset", "LengthOrPercentage",
    "computed::LengthOrPercentage::zero()",
    "parse_numbers_are_pixels",
    products="gecko",
    animatable=True,
    needs_context=False,
    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing")}

// Section 14 - Clipping, Masking and Compositing
${helpers.single_keyword("clip-rule", "nonzero evenodd",
                         products="gecko",
                         gecko_enum_prefix="StyleFillRule",
                         gecko_inexhaustive=True,
                         animatable=False,
                         spec="https://www.w3.org/TR/SVG11/masking.html#ClipRuleProperty")}

${helpers.predefined_type("marker-start", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          animatable="False",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

${helpers.predefined_type("marker-mid", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          animatable="False",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

${helpers.predefined_type("marker-end", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          animatable="False",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

<%helpers:longhand name="paint-order"
                   animatable="False"
                   products="gecko"
                   spec="https://www.w3.org/TR/SVG2/painting.html#PaintOrder">

    use values::computed::ComputedValueAsSpecified;
    use std::fmt;
    use style_traits::ToCss;
    use values::HasViewportPercentage;

    pub const NORMAL: u8 = 0;
    pub const FILL: u8 = 1;
    pub const STROKE: u8 = 2;
    pub const MARKERS: u8 = 3;

    // number of bits for each component
    pub const SHIFT: u8 = 2;
    // mask with above bits set
    pub const MASK: u8 = 0b11;
    // number of non-normal keyword values
    pub const COUNT: u8 = 3;
    // all keywords
    pub const ALL: [u8; 3] = [FILL, STROKE, MARKERS];

    /// Represented as a six-bit field, of 3 two-bit pairs
    ///
    /// Each pair can be set to FILL, STROKE, or MARKERS
    /// Lowest significant bit pairs are highest priority.
    ///  `normal` is the empty bitfield. The three pairs are
    /// never zero in any case other than `normal`.
    ///
    /// Higher priority values, i.e. the values specified first,
    /// will be painted first (and may be covered by paintings of lower priority)
    #[derive(PartialEq, Clone, Copy, Debug)]
    #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
    pub struct SpecifiedValue(pub u8);

    pub mod computed_value {
        pub use super::SpecifiedValue as T;
    }

    pub fn get_initial_value() -> SpecifiedValue {
      SpecifiedValue(NORMAL)
    }

    impl SpecifiedValue {
        pub fn bits_at(&self, pos: u8) -> u8 {
            (self.0 >> pos * SHIFT) & MASK
        }
    }

    pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
        if let Ok(()) = input.try(|i| i.expect_ident_matching("normal")) {
            Ok(SpecifiedValue(0))
        } else {
            let mut value = 0;
            // bitfield representing what we've seen so far
            // bit 1 is fill, bit 2 is stroke, bit 3 is markers
            let mut seen = 0;
            let mut pos = 0;

            loop {

                let result = input.try(|i| {
                    match_ignore_ascii_case! { i.expect_ident()?,
                        "fill" => Ok(FILL),
                        "stroke" => Ok(STROKE),
                        "markers" => Ok(MARKERS),
                        _ => Err(())
                    }
                });

                match result {
                    Ok(val) => {
                        if (seen & (1 << val)) != 0 {
                            // don't parse the same ident twice
                            return Err(())
                        } else {
                            value |= val << (pos * SHIFT);
                            seen |= 1 << val;
                            pos += 1;
                        }
                    }
                    Err(()) => break,
                }
            }

            if value == 0 {
                // couldn't find any keyword
                Err(())
            } else {
                // fill in rest
                for i in pos..COUNT {
                    for paint in &ALL {
                        // if not seen, set bit at position, mark as seen
                        if (seen & (1 << paint)) == 0 {
                            seen |= 1 << paint;
                            value |= paint << (i * SHIFT);
                            break;
                        }
                    }
                }

                Ok(SpecifiedValue(value))
            }
        }
    }

    impl ToCss for SpecifiedValue {
        fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
            if self.0 == 0 {
                return dest.write_str("normal")
            }

            for pos in 0..COUNT {
                if pos != 0 {
                    dest.write_str(" ")?
                }
                match self.bits_at(pos) {
                    FILL => dest.write_str("fill")?,
                    STROKE => dest.write_str("stroke")?,
                    MARKERS => dest.write_str("markers")?,
                    _ => unreachable!(),
                }
            }
            Ok(())
        }
    }

    no_viewport_percentage!(SpecifiedValue);

    impl ComputedValueAsSpecified for SpecifiedValue { }
</%helpers:longhand>

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

// SVG 1.1 (Second Edition)
// https://www.w3.org/TR/SVG/
<% data.new_style_struct("InheritedSVG",
                         inherited=True,
                         gecko_name="SVG") %>

// Section 10 - Text

${helpers.single_keyword("text-anchor",
                         "start middle end",
                         products="gecko",
                         animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG/text.html#TextAnchorProperty")}

// Section 11 - Painting: Filling, Stroking and Marker Symbols
${helpers.single_keyword("color-interpolation",
                         "srgb auto linearrgb",
                         products="gecko",
                         animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationProperty")}

${helpers.single_keyword("color-interpolation-filters", "linearrgb auto srgb",
                         products="gecko",
                         gecko_constant_prefix="NS_STYLE_COLOR_INTERPOLATION",
                         animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationFiltersProperty")}

${helpers.predefined_type(
    "fill", "SVGPaint",
    "::values::computed::SVGPaint::black()",
    products="gecko",
    animation_value_type="IntermediateSVGPaint",
    boxed=True,
    spec="https://www.w3.org/TR/SVG2/painting.html#SpecifyingFillPaint")}

${helpers.predefined_type("fill-opacity", "SVGOpacity", "Default::default()",
                          products="gecko", animation_value_type="ComputedValue",
                          spec="https://www.w3.org/TR/SVG11/painting.html#FillOpacityProperty")}

${helpers.single_keyword("fill-rule", "nonzero evenodd",
                         gecko_enum_prefix="StyleFillRule",
                         products="gecko", animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#FillRuleProperty")}

${helpers.single_keyword("shape-rendering",
                         "auto optimizespeed crispedges geometricprecision",
                         products="gecko",
                         animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#ShapeRenderingProperty")}

${helpers.predefined_type(
    "stroke", "SVGPaint",
    "Default::default()",
    products="gecko",
    animation_value_type="IntermediateSVGPaint",
    boxed=True,
    spec="https://www.w3.org/TR/SVG2/painting.html#SpecifyingStrokePaint")}

${helpers.predefined_type(
    "stroke-width", "SVGWidth",
    "::values::computed::NonNegativeLength::new(1.).into()",
    products="gecko",
    boxed=not RUSTC_HAS_PR45225,
    animation_value_type="::values::computed::SVGWidth",
    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeWidth")}

${helpers.single_keyword("stroke-linecap", "butt round square",
                         products="gecko", animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty")}

${helpers.single_keyword("stroke-linejoin", "miter round bevel",
                         products="gecko", animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty")}

${helpers.predefined_type("stroke-miterlimit", "GreaterThanOrEqualToOneNumber",
                          "From::from(4.0)",
                          products="gecko",
                          animation_value_type="::values::computed::GreaterThanOrEqualToOneNumber",
                          spec="https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty")}

${helpers.predefined_type("stroke-opacity", "SVGOpacity", "Default::default()",
                          products="gecko", animation_value_type="ComputedValue",
                          spec="https://www.w3.org/TR/SVG11/painting.html#StrokeOpacityProperty")}

${helpers.predefined_type(
    "stroke-dasharray",
    "SVGStrokeDashArray",
    "Default::default()",
    products="gecko",
    animation_value_type="::values::computed::SVGStrokeDashArray",
    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing",
)}

${helpers.predefined_type(
    "stroke-dashoffset", "SVGLength",
    "Au(0).into()",
    products="gecko",
    boxed=not RUSTC_HAS_PR45225,
    animation_value_type="ComputedValue",
    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing")}

// Section 14 - Clipping, Masking and Compositing
${helpers.single_keyword("clip-rule", "nonzero evenodd",
                         products="gecko",
                         gecko_enum_prefix="StyleFillRule",
                         animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/masking.html#ClipRuleProperty")}

${helpers.predefined_type("marker-start", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          boxed= product == "gecko",
                          animation_value_type="discrete",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

${helpers.predefined_type("marker-mid", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          boxed= product == "gecko",
                          animation_value_type="discrete",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

${helpers.predefined_type("marker-end", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          boxed= product == "gecko",
                          animation_value_type="discrete",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

${helpers.predefined_type("paint-order", "SVGPaintOrder", "computed::SVGPaintOrder::normal()",
                          products="gecko",
                          animation_value_type="discrete",
                          gecko_pref="svg.paint-order.enabled",
                          spec="https://www.w3.org/TR/SVG2/painting.html#PaintOrder")}

${helpers.predefined_type("-moz-context-properties",
                          "MozContextProperties",
                          initial_value=None,
                          vector=True,
                          animation_value_type="none",
                          products="gecko",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties)",
                          allow_empty=True)}
<commit_msg>Remove reference to the Gecko pref svg.paint-order.enabled<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

<%namespace name="helpers" file="/helpers.mako.rs" />

// SVG 1.1 (Second Edition)
// https://www.w3.org/TR/SVG/
<% data.new_style_struct("InheritedSVG",
                         inherited=True,
                         gecko_name="SVG") %>

// Section 10 - Text

${helpers.single_keyword("text-anchor",
                         "start middle end",
                         products="gecko",
                         animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG/text.html#TextAnchorProperty")}

// Section 11 - Painting: Filling, Stroking and Marker Symbols
${helpers.single_keyword("color-interpolation",
                         "srgb auto linearrgb",
                         products="gecko",
                         animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationProperty")}

${helpers.single_keyword("color-interpolation-filters", "linearrgb auto srgb",
                         products="gecko",
                         gecko_constant_prefix="NS_STYLE_COLOR_INTERPOLATION",
                         animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#ColorInterpolationFiltersProperty")}

${helpers.predefined_type(
    "fill", "SVGPaint",
    "::values::computed::SVGPaint::black()",
    products="gecko",
    animation_value_type="IntermediateSVGPaint",
    boxed=True,
    spec="https://www.w3.org/TR/SVG2/painting.html#SpecifyingFillPaint")}

${helpers.predefined_type("fill-opacity", "SVGOpacity", "Default::default()",
                          products="gecko", animation_value_type="ComputedValue",
                          spec="https://www.w3.org/TR/SVG11/painting.html#FillOpacityProperty")}

${helpers.single_keyword("fill-rule", "nonzero evenodd",
                         gecko_enum_prefix="StyleFillRule",
                         products="gecko", animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#FillRuleProperty")}

${helpers.single_keyword("shape-rendering",
                         "auto optimizespeed crispedges geometricprecision",
                         products="gecko",
                         animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#ShapeRenderingProperty")}

${helpers.predefined_type(
    "stroke", "SVGPaint",
    "Default::default()",
    products="gecko",
    animation_value_type="IntermediateSVGPaint",
    boxed=True,
    spec="https://www.w3.org/TR/SVG2/painting.html#SpecifyingStrokePaint")}

${helpers.predefined_type(
    "stroke-width", "SVGWidth",
    "::values::computed::NonNegativeLength::new(1.).into()",
    products="gecko",
    boxed=not RUSTC_HAS_PR45225,
    animation_value_type="::values::computed::SVGWidth",
    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeWidth")}

${helpers.single_keyword("stroke-linecap", "butt round square",
                         products="gecko", animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty")}

${helpers.single_keyword("stroke-linejoin", "miter round bevel",
                         products="gecko", animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty")}

${helpers.predefined_type("stroke-miterlimit", "GreaterThanOrEqualToOneNumber",
                          "From::from(4.0)",
                          products="gecko",
                          animation_value_type="::values::computed::GreaterThanOrEqualToOneNumber",
                          spec="https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty")}

${helpers.predefined_type("stroke-opacity", "SVGOpacity", "Default::default()",
                          products="gecko", animation_value_type="ComputedValue",
                          spec="https://www.w3.org/TR/SVG11/painting.html#StrokeOpacityProperty")}

${helpers.predefined_type(
    "stroke-dasharray",
    "SVGStrokeDashArray",
    "Default::default()",
    products="gecko",
    animation_value_type="::values::computed::SVGStrokeDashArray",
    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing",
)}

${helpers.predefined_type(
    "stroke-dashoffset", "SVGLength",
    "Au(0).into()",
    products="gecko",
    boxed=not RUSTC_HAS_PR45225,
    animation_value_type="ComputedValue",
    spec="https://www.w3.org/TR/SVG2/painting.html#StrokeDashing")}

// Section 14 - Clipping, Masking and Compositing
${helpers.single_keyword("clip-rule", "nonzero evenodd",
                         products="gecko",
                         gecko_enum_prefix="StyleFillRule",
                         animation_value_type="discrete",
                         spec="https://www.w3.org/TR/SVG11/masking.html#ClipRuleProperty")}

${helpers.predefined_type("marker-start", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          boxed= product == "gecko",
                          animation_value_type="discrete",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

${helpers.predefined_type("marker-mid", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          boxed= product == "gecko",
                          animation_value_type="discrete",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

${helpers.predefined_type("marker-end", "UrlOrNone", "Either::Second(None_)",
                          products="gecko",
                          boxed= product == "gecko",
                          animation_value_type="discrete",
                          spec="https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties")}

${helpers.predefined_type("paint-order", "SVGPaintOrder", "computed::SVGPaintOrder::normal()",
                          products="gecko",
                          animation_value_type="discrete",
                          spec="https://www.w3.org/TR/SVG2/painting.html#PaintOrder")}

${helpers.predefined_type("-moz-context-properties",
                          "MozContextProperties",
                          initial_value=None,
                          vector=True,
                          animation_value_type="none",
                          products="gecko",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-context-properties)",
                          allow_empty=True)}
<|endoftext|>


<commit_before>use std::ops::Deref;
use std::sync::Arc;
use std::sync::{Mutex, MutexGuard};

use owning_ref::{ArcRef, OwningHandle};
use unicode_width::UnicodeWidthStr;

use crate::align::*;
use crate::theme::Effect;
use crate::utils::lines::spans::{LinesIterator, Row};
use crate::utils::markup::StyledString;
use crate::view::{SizeCache, View};
use crate::{Printer, Vec2, With, XY};

/// Provides access to the content of a [`TextView`].
///
/// Cloning this object will still point to the same content.
///
/// [`TextView`]: struct.TextView.html
///
/// # Examples
///
/// ```rust
/// # use cursive::views::{TextView, TextContent};
/// let mut content = TextContent::new("content");
/// let view = TextView::new_with_content(content.clone());
///
/// // Later, possibly in a different thread
/// content.set_content("new content");
/// assert!(view.get_content().source().contains("new"));
/// ```
#[derive(Clone)]
pub struct TextContent {
    content: Arc<Mutex<TextContentInner>>,
}

impl TextContent {
    /// Creates a new text content around the given value.
    ///
    /// Parses the given value.
    pub fn new<S>(content: S) -> Self
    where
        S: Into<StyledString>,
    {
        let content = content.into();

        TextContent {
            content: Arc::new(Mutex::new(TextContentInner {
                content,
                size_cache: None,
            })),
        }
    }
}

/// A reference to the text content.
///
/// This can be deref'ed into a [`StyledString`].
///
/// [`StyledString`]: ../utils/markup/type.StyledString.html
///
/// This keeps the content locked. Do not store this!
pub struct TextContentRef {
    handle: OwningHandle<
        ArcRef<Mutex<TextContentInner>>,
        MutexGuard<'static, TextContentInner>,
    >,
}

impl Deref for TextContentRef {
    type Target = StyledString;

    fn deref(&self) -> &StyledString {
        &self.handle.content
    }
}

impl TextContent {
    /// Replaces the content with the given value.
    pub fn set_content<S>(&mut self, content: S)
    where
        S: Into<StyledString>,
    {
        self.with_content(|c| *c = content.into());
    }

    /// Append `content` to the end of a `TextView`.
    pub fn append<S>(&mut self, content: S)
    where
        S: Into<StyledString>,
    {
        self.with_content(|c| c.append(content))
    }

    /// Returns a reference to the content.
    ///
    /// This locks the data while the returned value is alive,
    /// so don't keep it too long.
    pub fn get_content(&self) -> TextContentRef {
        TextContentInner::get_content(&self.content)
    }

    fn with_content<F, O>(&mut self, f: F) -> O
    where
        F: FnOnce(&mut StyledString) -> O,
    {
        let mut lock = self.content.lock().unwrap();

        let out = f(&mut lock.content);

        lock.size_cache = None;

        out
    }
}

/// Internel representation of the content for a `TextView`.
///
/// This is mostly just a `StyledString`.
///
/// Can be shared (through a `Arc<Mutex>`).
struct TextContentInner {
    // content: String,
    content: StyledString,

    // We keep the cache here so it can be busted when we change the content.
    size_cache: Option<XY<SizeCache>>,
}

impl TextContentInner {
    /// From a shareable content (Arc + Mutex), return a
    fn get_content(content: &Arc<Mutex<TextContentInner>>) -> TextContentRef {
        let arc_ref: ArcRef<Mutex<TextContentInner>> =
            ArcRef::new(Arc::clone(content));

        TextContentRef {
            handle: OwningHandle::new_with_fn(arc_ref, |mutex| unsafe {
                (*mutex).lock().unwrap()
            }),
        }
    }

    fn is_cache_valid(&self, size: Vec2) -> bool {
        match self.size_cache {
            None => false,
            Some(ref last) => last.x.accept(size.x) && last.y.accept(size.y),
        }
    }
}

/// A simple view showing a fixed text.
///
/// # Examples
///
/// ```rust
/// # use cursive::Cursive;
/// # use cursive::views::TextView;
/// let mut siv = Cursive::dummy();
///
/// siv.add_layer(TextView::new("Hello world!"));
/// ```
pub struct TextView {
    // content: String,
    content: Arc<Mutex<TextContentInner>>,
    rows: Vec<Row>,

    align: Align,
    effect: Effect,

    // True if we can wrap long lines.
    wrap: bool,

    // ScrollBase make many scrolling-related things easier
    last_size: Vec2,
    width: Option<usize>,
}

impl TextView {
    /// Creates a new TextView with the given content.
    pub fn new<S>(content: S) -> Self
    where
        S: Into<StyledString>,
    {
        Self::new_with_content(TextContent::new(content))
    }

    /// Creates a new TextView using the given `Arc<Mutex<String>>`.
    ///
    /// If you kept a clone of the given content, you'll be able to update it
    /// remotely.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cursive::views::{TextView, TextContent};
    /// let mut content = TextContent::new("content");
    /// let view = TextView::new_with_content(content.clone());
    ///
    /// // Later, possibly in a different thread
    /// content.set_content("new content");
    /// assert!(view.get_content().source().contains("new"));
    /// ```
    pub fn new_with_content(content: TextContent) -> Self {
        TextView {
            content: content.content,
            effect: Effect::Simple,
            rows: Vec::new(),
            wrap: true,
            align: Align::top_left(),
            last_size: Vec2::zero(),
            width: None,
        }
    }

    /// Creates a new empty `TextView`.
    pub fn empty() -> Self {
        TextView::new("")
    }

    /// Sets the effect for the entire content.
    pub fn set_effect(&mut self, effect: Effect) {
        self.effect = effect;
    }

    /// Sets the effect for the entire content.
    ///
    /// Chainable variant.
    pub fn effect(self, effect: Effect) -> Self {
        self.with(|s| s.set_effect(effect))
    }

    /// Disables content wrap for this view.
    ///
    /// This may be useful if you want horizontal scrolling.
    pub fn no_wrap(self) -> Self {
        self.with(|s| s.set_content_wrap(false))
    }

    /// Controls content wrap for this view.
    ///
    /// If `true` (the default), text will wrap long lines when needed.
    pub fn set_content_wrap(&mut self, wrap: bool) {
        self.wrap = wrap;
    }

    /// Sets the horizontal alignment for this view.
    pub fn h_align(mut self, h: HAlign) -> Self {
        self.align.h = h;

        self
    }

    /// Sets the vertical alignment for this view.
    pub fn v_align(mut self, v: VAlign) -> Self {
        self.align.v = v;

        self
    }

    /// Sets the alignment for this view.
    pub fn align(mut self, a: Align) -> Self {
        self.align = a;

        self
    }

    /// Center the text horizontally and vertically inside the view.
    pub fn center(mut self) -> Self {
        self.align = Align::center();
        self
    }

    /// Replace the text in this view.
    ///
    /// Chainable variant.
    pub fn content<S>(self, content: S) -> Self
    where
        S: Into<StyledString>,
    {
        self.with(|s| s.set_content(content))
    }

    /// Replace the text in this view.
    pub fn set_content<S>(&mut self, content: S)
    where
        S: Into<StyledString>,
    {
        self.content.lock().unwrap().content = content.into();
        self.invalidate();
    }

    /// Append `content` to the end of a `TextView`.
    pub fn append<S>(&mut self, content: S)
    where
        S: Into<StyledString>,
    {
        self.content.lock().unwrap().content.append(content.into());
        self.invalidate();
    }

    /// Returns the current text in this view.
    pub fn get_content(&self) -> TextContentRef {
        TextContentInner::get_content(&self.content)
    }

    /// Returns a shared reference to the content, allowing content mutation.
    pub fn get_shared_content(&mut self) -> TextContent {
        // We take &mut here without really needing it,
        // because it sort of "makes sense".

        TextContent {
            content: Arc::clone(&self.content),
        }
    }

    // This must be non-destructive, as it may be called
    // multiple times during layout.
    fn compute_rows(&mut self, size: Vec2) {
        let size = if self.wrap { size } else { Vec2::max_value() };

        let mut content = self.content.lock().unwrap();
        if content.is_cache_valid(size) {
            return;
        }

        // Completely bust the cache
        // Just in case we fail, we don't want to leave a bad cache.
        content.size_cache = None;

        if size.x == 0 {
            // Nothing we can do at this point.
            return;
        }

        self.rows = LinesIterator::new(&content.content, size.x).collect();

        // Desired width
        self.width = self.rows.iter().map(|row| row.width).max();
    }

    // Invalidates the cache, so next call will recompute everything.
    fn invalidate(&mut self) {
        let mut content = self.content.lock().unwrap();
        content.size_cache = None;
    }
}

impl View for TextView {
    fn draw(&self, printer: &Printer<'_, '_>) {
        let h = self.rows.len();
        // If the content is smaller than the view, align it somewhere.
        let offset = self.align.v.get_offset(h, printer.size.y);
        let printer = &printer.offset((0, offset));

        let content = self.content.lock().unwrap();

        printer.with_effect(self.effect, |printer| {
            for (y, row) in self.rows.iter().enumerate() {
                let l = row.width;
                let mut x = self.align.h.get_offset(l, printer.size.x);

                for span in row.resolve(&content.content) {
                    printer.with_style(*span.attr, |printer| {
                        printer.print((x, y), span.content);
                        x += span.content.width();
                    });
                }
            }
        });
    }

    fn needs_relayout(&self) -> bool {
        let content = self.content.lock().unwrap();
        content.size_cache.is_none()
    }

    fn required_size(&mut self, size: Vec2) -> Vec2 {
        self.compute_rows(size);

        Vec2::new(self.width.unwrap_or(0), self.rows.len())
    }

    fn layout(&mut self, size: Vec2) {
        // Compute the text rows.
        self.last_size = size;
        self.compute_rows(size);

        // The entire "virtual" size (includes all rows)
        let my_size = Vec2::new(self.width.unwrap_or(0), self.rows.len());

        // Build a fresh cache.
        let mut content = self.content.lock().unwrap();
        content.size_cache = Some(SizeCache::build(my_size, size));
    }
}
<commit_msg>Fix out-of-bounds access on TextView<commit_after>use std::cell::RefCell;
use std::ops::Deref;
use std::ptr;
use std::sync::Arc;
use std::sync::{Mutex, MutexGuard};

use owning_ref::{ArcRef, OwningHandle};
use unicode_width::UnicodeWidthStr;

use crate::align::*;
use crate::theme::Effect;
use crate::utils::lines::spans::{LinesIterator, Row};
use crate::utils::markup::StyledString;
use crate::view::{SizeCache, View};
use crate::{Printer, Vec2, With, XY};

// Content type used internally for caching and storage
type InnerContentType = RefCell<Arc<StyledString>>;

/// Provides access to the content of a [`TextView`].
///
/// Cloning this object will still point to the same content.
///
/// [`TextView`]: struct.TextView.html
///
/// # Examples
///
/// ```rust
/// # use cursive::views::{TextView, TextContent};
/// let mut content = TextContent::new("content");
/// let view = TextView::new_with_content(content.clone());
///
/// // Later, possibly in a different thread
/// content.set_content("new content");
/// assert!(view.get_content().source().contains("new"));
/// ```
#[derive(Clone)]
pub struct TextContent {
    content: Arc<Mutex<TextContentInner>>,
}

impl TextContent {
    /// Creates a new text content around the given value.
    ///
    /// Parses the given value.
    pub fn new<S>(content: S) -> Self
    where
        S: Into<StyledString>,
    {
        let content = Arc::new(content.into());

        TextContent {
            content: Arc::new(Mutex::new(TextContentInner {
                content_value: RefCell::new(content),
                content_cache: RefCell::new(Arc::new(StyledString::default())),
                size_cache: None,
            })),
        }
    }
}

/// A reference to the text content.
///
/// This can be deref'ed into a [`StyledString`].
///
/// [`StyledString`]: ../utils/markup/type.StyledString.html
///
/// This keeps the content locked. Do not store this!

pub struct TextContentRef {
    _handle: OwningHandle<
        ArcRef<Mutex<TextContentInner>>,
        MutexGuard<'static, TextContentInner>,
    >,
    // We also need to keep a copy of Arc so `deref` can return
    // a reference to the `StyledString`
    data: Arc<StyledString>,
}

impl Deref for TextContentRef {
    type Target = StyledString;

    fn deref(&self) -> &StyledString {
        self.data.as_ref()
    }
}

impl TextContent {
    /// Replaces the content with the given value.
    pub fn set_content<S>(&mut self, content: S)
    where
        S: Into<StyledString>,
    {
        self.with_content(|c| {
            c.content_value.replace(Arc::new(content.into()))
        });
    }

    /// Append `content` to the end of a `TextView`.
    pub fn append<S>(&mut self, content: S)
    where
        S: Into<StyledString>,
    {
        self.with_content(|c| {
            // This will only clone content if content_cached and content_value
            // are sharing the same underlying Rc.
            if c.is_content_shared() {
                c.content_value
                    .replace(Arc::new(c.get_value().as_ref().clone()));
            };
            Arc::get_mut(&mut c.content_value.borrow_mut())
                .expect("should not have a shared content here")
                .append(content)
        })
    }

    /// Returns a reference to the content.
    ///
    /// This locks the data while the returned value is alive,
    /// so don't keep it too long.
    pub fn get_content(&self) -> TextContentRef {
        TextContentInner::get_content(&self.content)
    }

    fn with_content<F, O>(&mut self, f: F) -> O
    where
        F: FnOnce(&mut TextContentInner) -> O,
    {
        let mut lock = self.content.lock().unwrap();

        let out = f(&mut lock);

        lock.size_cache = None;

        out
    }
}

/// Internel representation of the content for a `TextView`.
///
/// This is mostly just a `StyledString`.
///
/// Can be shared (through a `Arc<Mutex>`).
struct TextContentInner {
    // content: String,
    content_value: InnerContentType,
    content_cache: InnerContentType,

    // We keep the cache here so it can be busted when we change the content.
    size_cache: Option<XY<SizeCache>>,
}

impl TextContentInner {
    /// From a shareable content (Arc + Mutex), return a
    fn get_content(content: &Arc<Mutex<TextContentInner>>) -> TextContentRef {
        let arc_ref: ArcRef<Mutex<TextContentInner>> =
            ArcRef::new(Arc::clone(content));

        let _handle = OwningHandle::new_with_fn(arc_ref, |mutex| unsafe {
            (*mutex).lock().unwrap()
        });

        let data = _handle.get_value();

        TextContentRef { _handle, data }
    }

    fn is_cache_valid(&self, size: Vec2) -> bool {
        match self.size_cache {
            None => false,
            Some(ref last) => last.x.accept(size.x) && last.y.accept(size.y),
        }
    }

    fn get_value(&self) -> Arc<StyledString> {
        Arc::clone(&*self.content_value.borrow())
    }

    fn get_cache(&self) -> Arc<StyledString> {
        Arc::clone(&*self.content_cache.borrow())
    }

    fn is_content_shared(&self) -> bool {
        ptr::eq(self.get_value().as_ref(), self.get_cache().as_ref())
    }
}

/// A simple view showing a fixed text.
///
/// # Examples
///
/// ```rust
/// # use cursive::Cursive;
/// # use cursive::views::TextView;
/// let mut siv = Cursive::dummy();
///
/// siv.add_layer(TextView::new("Hello world!"));
/// ```
pub struct TextView {
    // content: String,
    content: TextContent,
    rows: Vec<Row>,

    align: Align,
    effect: Effect,

    // True if we can wrap long lines.
    wrap: bool,

    // ScrollBase make many scrolling-related things easier
    last_size: Vec2,
    width: Option<usize>,
}

impl TextView {
    /// Creates a new TextView with the given content.
    pub fn new<S>(content: S) -> Self
    where
        S: Into<StyledString>,
    {
        Self::new_with_content(TextContent::new(content))
    }

    /// Creates a new TextView using the given `Arc<Mutex<String>>`.
    ///
    /// If you kept a clone of the given content, you'll be able to update it
    /// remotely.
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use cursive::views::{TextView, TextContent};
    /// let mut content = TextContent::new("content");
    /// let view = TextView::new_with_content(content.clone());
    ///
    /// // Later, possibly in a different thread
    /// content.set_content("new content");
    /// assert!(view.get_content().source().contains("new"));
    /// ```
    pub fn new_with_content(content: TextContent) -> Self {
        TextView {
            content: content,
            effect: Effect::Simple,
            rows: Vec::new(),
            wrap: true,
            align: Align::top_left(),
            last_size: Vec2::zero(),
            width: None,
        }
    }

    /// Creates a new empty `TextView`.
    pub fn empty() -> Self {
        TextView::new("")
    }

    /// Sets the effect for the entire content.
    pub fn set_effect(&mut self, effect: Effect) {
        self.effect = effect;
    }

    /// Sets the effect for the entire content.
    ///
    /// Chainable variant.
    pub fn effect(self, effect: Effect) -> Self {
        self.with(|s| s.set_effect(effect))
    }

    /// Disables content wrap for this view.
    ///
    /// This may be useful if you want horizontal scrolling.
    pub fn no_wrap(self) -> Self {
        self.with(|s| s.set_content_wrap(false))
    }

    /// Controls content wrap for this view.
    ///
    /// If `true` (the default), text will wrap long lines when needed.
    pub fn set_content_wrap(&mut self, wrap: bool) {
        self.wrap = wrap;
    }

    /// Sets the horizontal alignment for this view.
    pub fn h_align(mut self, h: HAlign) -> Self {
        self.align.h = h;

        self
    }

    /// Sets the vertical alignment for this view.
    pub fn v_align(mut self, v: VAlign) -> Self {
        self.align.v = v;

        self
    }

    /// Sets the alignment for this view.
    pub fn align(mut self, a: Align) -> Self {
        self.align = a;

        self
    }

    /// Center the text horizontally and vertically inside the view.
    pub fn center(mut self) -> Self {
        self.align = Align::center();
        self
    }

    /// Replace the text in this view.
    ///
    /// Chainable variant.
    pub fn content<S>(self, content: S) -> Self
    where
        S: Into<StyledString>,
    {
        self.with(|s| s.set_content(content))
    }

    /// Replace the text in this view.
    pub fn set_content<S>(&mut self, content: S)
    where
        S: Into<StyledString>,
    {
        self.content.set_content(content);
    }

    /// Append `content` to the end of a `TextView`.
    pub fn append<S>(&mut self, content: S)
    where
        S: Into<StyledString>,
    {
        self.content.append(content);
    }

    /// Returns the current text in this view.
    pub fn get_content(&self) -> TextContentRef {
        TextContentInner::get_content(&self.content.content)
    }

    /// Returns a shared reference to the content, allowing content mutation.
    pub fn get_shared_content(&mut self) -> TextContent {
        // We take &mut here without really needing it,
        // because it sort of "makes sense".
        TextContent {
            content: Arc::clone(&self.content.content),
        }
    }

    // This must be non-destructive, as it may be called
    // multiple times during layout.
    fn compute_rows(&mut self, size: Vec2) {
        let size = if self.wrap { size } else { Vec2::max_value() };

        let mut content = self.content.content.lock().unwrap();
        if content.is_cache_valid(size) {
            return;
        }

        // Completely bust the cache
        // Just in case we fail, we don't want to leave a bad cache.
        content.size_cache = None;
        content
            .content_cache
            .replace(content.content_value.borrow().clone());

        if size.x == 0 {
            // Nothing we can do at this point.
            return;
        }

        self.rows =
            LinesIterator::new(content.get_cache().as_ref(), size.x).collect();

        // Desired width
        self.width = self.rows.iter().map(|row| row.width).max();
    }
}

impl View for TextView {
    fn draw(&self, printer: &Printer<'_, '_>) {
        let h = self.rows.len();
        // If the content is smaller than the view, align it somewhere.
        let offset = self.align.v.get_offset(h, printer.size.y);
        let printer = &printer.offset((0, offset));

        let content = self.content.content.lock().unwrap();

        printer.with_effect(self.effect, |printer| {
            for (y, row) in self.rows.iter().enumerate() {
                let l = row.width;
                let mut x = self.align.h.get_offset(l, printer.size.x);

                for span in row.resolve(content.get_cache().as_ref()) {
                    printer.with_style(*span.attr, |printer| {
                        printer.print((x, y), span.content);
                        x += span.content.width();
                    });
                }
            }
        });
    }

    fn needs_relayout(&self) -> bool {
        let content = self.content.content.lock().unwrap();
        content.size_cache.is_none()
    }

    fn required_size(&mut self, size: Vec2) -> Vec2 {
        self.compute_rows(size);

        Vec2::new(self.width.unwrap_or(0), self.rows.len())
    }

    fn layout(&mut self, size: Vec2) {
        // Compute the text rows.
        self.last_size = size;
        self.compute_rows(size);

        // The entire "virtual" size (includes all rows)
        let my_size = Vec2::new(self.width.unwrap_or(0), self.rows.len());

        // Build a fresh cache.
        let mut content = self.content.content.lock().unwrap();
        content.size_cache = Some(SizeCache::build(my_size, size));
    }
}
<|endoftext|>


<commit_before>//
// base64.rs - base64 implementation
//
// The Base64 Alphabet
//
//  Value Encoding  Value Encoding  Value Encoding  Value Encoding
//      0 A            17 R            34 i            51 z
//      1 B            18 S            35 j            52 0
//      2 C            19 T            36 k            53 1
//      3 D            20 U            37 l            54 2
//      4 E            21 V            38 m            55 3
//      5 F            22 W            39 n            56 4
//      6 G            23 X            40 o            57 5
//      7 H            24 Y            41 p            58 6
//      8 I            25 Z            42 q            59 7
//      9 J            26 a            43 r            60 8
//     10 K            27 b            44 s            61 9
//     11 L            28 c            45 t            62 +
//     12 M            29 d            46 u            63 /
//     13 N            30 e            47 v
//     14 O            31 f            48 w         (pad) =
//     15 P            32 g            49 x
//     16 Q            33 h            50 y
//
// The "URL and Filename safe" Base 64 Alphabet
//
// Value Encoding  Value Encoding  Value Encoding  Value Encoding
//     0 A            17 R            34 i            51 z
//     1 B            18 S            35 j            52 0
//     2 C            19 T            36 k            53 1
//     3 D            20 U            37 l            54 2
//     4 E            21 V            38 m            55 3
//     5 F            22 W            39 n            56 4
//     6 G            23 X            40 o            57 5
//     7 H            24 Y            41 p            58 6
//     8 I            25 Z            42 q            59 7
//     9 J            26 a            43 r            60 8
//    10 K            27 b            44 s            61 9
//    11 L            28 c            45 t            62 - (minus)
//    12 M            29 d            46 u            63 _ (underline)
//    13 N            30 e            47 v
//    14 O            31 f            48 w
//    15 P            32 g            49 x
//    16 Q            33 h            50 y         (pad) =
//

use std;

import std::map;

export base64, mk;

const padd: u8 = 61u8;

iface base64 {
    fn encode(src: [u8]) -> [u8];
    fn decode(src: [u8]) -> [u8];
    fn urlsafe_encode(src: [u8]) -> [u8];
    fn urlsafe_decode(src: [u8]) -> [u8];
}

fn mk() -> base64 {
    type _base64 = {table: [u8]};

    impl of base64 for _base64 {
        fn encode(src: [u8]) -> [u8] {
            b64encode(self.table + [43u8, 47u8], src)
        }
        fn decode(src: [u8]) -> [u8] {
            b64decode(self.table + [43u8, 47u8], src)
        }
        fn urlsafe_encode(src: [u8]) -> [u8] {
            b64encode(self.table + [45u8, 95u8], src)
        }
        fn urlsafe_decode(src: [u8]) -> [u8] {
            b64decode(self.table + [45u8, 95u8], src)
        }
    }

    let table = vec::init_elt_mut(62u, 0u8), i = 0u8;
    u8::range(65u8, 91u8)  { |j| table[i] = j; i += 1u8; }
    u8::range(97u8, 123u8) { |j| table[i] = j; i += 1u8; }
    u8::range(48u8, 58u8)  { |j| table[i] = j; i += 1u8; }

    {table: vec::from_mut(table)} as base64
}

fn b64idx(x: u8, y: u8, z: u8) -> u8 {
    if 65u8 <= x && x <= 90u8 { x - 65u8 }
    else if 97u8 <= x && x <= 122u8 { x - 97u8 + 26u8 }
    else if 48u8 <= x && x <= 57u8  { x - 48u8 + 52u8 }
    else if x == y { 62u8 }
    else if x == z { 63u8 }
    else { fail "malformed base64 string"; }
}

fn b64encode(table: [u8], src: [u8]) -> [u8] {
    let srclen = vec::len(src);
    let targ = if srclen % 3u == 0u {
        vec::init_elt_mut((srclen / 3u) * 4u, 0u8)
    } else {
        vec::init_elt_mut((srclen / 3u + 1u) * 4u, 0u8)
    };
    let input = vec::init_elt_mut(3u, 0u8);
    let output = vec::init_elt_mut(4u, 0u8);
    let curr = 0u, src_curr = 0u;

    while srclen > 2u {
        input[0] = src[src_curr];
        input[1] = src[src_curr + 1u];
        input[2] = src[src_curr + 2u];
        srclen -= 3u; src_curr += 3u;

        output[0] = input[0] >> 2u8;
        output[1] = ((input[0] &  3u8) << 4u8) | (input[1] >> 4u8);
        output[2] = ((input[1] & 15u8) << 2u8) | (input[2] >> 6u8);
        output[3] = input[2] & 63u8;

        targ[curr + 0u] = table[output[0]];
        targ[curr + 1u] = table[output[1]];
        targ[curr + 2u] = table[output[2]];
        targ[curr + 3u] = table[output[3]];
        curr += 4u;
    }

    if srclen != 0u {
        input[0] = 0u8; input[1] = 0u8; input[2] = 0u8;

        alt srclen {
          1u {input[0] = src[src_curr];}
          2u {input[0] = src[src_curr];
              input[1] = src[src_curr + 1u];}
          _  { }
        }

        output[0] = input[0] >> 2u8;
        output[1] = ((input[0] &  3u8) << 4u8) | (input[1] >> 4u8);
        output[2] = ((input[1] & 15u8) << 2u8) | (input[2] >> 6u8);

        targ[curr + 0u] = table[output[0]];
        targ[curr + 1u] = table[output[1]];
        targ[curr + 2u] = if srclen == 1u { padd } else { table[output[2]] };
        targ[curr + 3u] = padd;
    }

    vec::from_mut(targ)
}

fn b64decode(table: [u8], src: [u8]) -> [u8] {
    let srclen = vec::len(src);

    if srclen % 4u != 0u { fail "malformed base64 string"; }
    if srclen == 0u { ret []; }

    let input  = vec::init_elt_mut(4u, 0u8);
    let output = vec::init_elt_mut(4u, 0u8);
    let targ = if src[srclen - 2u] == padd {
        vec::init_elt_mut(srclen / 4u * 3u - 2u, 0u8)
    } else if src[srclen - 1u] == padd {
        vec::init_elt_mut(srclen / 4u * 3u - 1u, 0u8)
    } else {
        vec::init_elt_mut(srclen / 4u * 3u, 0u8)
    };
    let curr = 0u, src_curr = 0u;

    while srclen > 4u {
        input[0] = src[src_curr];
        input[1] = src[src_curr + 1u];
        input[2] = src[src_curr + 2u];
        input[3] = src[src_curr + 3u];
        srclen -= 4u; src_curr += 4u;

        output[0] = b64idx(input[0], table[62], table[63]);
        output[1] = b64idx(input[1], table[62], table[63]);
        output[2] = b64idx(input[2], table[62], table[63]);
        output[3] = b64idx(input[3], table[62], table[63]);

        targ[curr + 0u] = (output[0] << 2u8) | (output[1] >> 4u8);
        targ[curr + 1u] = ((output[1] & 15u8) << 4u8) | (output[2] >> 2u8);
        targ[curr + 2u] = ((output[2] &  3u8) << 6u8) | output[3];
        curr += 3u;
    }

    if srclen == 4u {
        input[0] = src[src_curr];
        input[1] = src[src_curr + 1u];
        input[2] = src[src_curr + 2u];
        input[3] = src[src_curr + 3u];

        output[0] = b64idx(input[0], table[62], table[63]);
        output[1] = b64idx(input[1], table[62], table[63]);

        targ[curr + 0u] = (output[0] << 2u8) | (output[1] >> 4u8);

        if input[2] == padd { ret vec::from_mut(targ); }

        output[2] = b64idx(input[2], table[62], table[63]);

        targ[curr + 1u] = ((output[1] & 15u8) << 4u8) | (output[2] >> 2u8);

        if input[3] == padd { ret vec::from_mut(targ); }

        output[3] = b64idx(input[3], table[62], table[63]);

        targ[curr + 2u] = ((output[2] & 3u8) << 6u8) | output[3];
    }

    vec::from_mut(targ)
}

#[cfg(test)]
mod tests {
    import core::str::{from_bytes, bytes};
    enum mode {
        t_decode,
        t_encode,
        t_urlsafe_encode,
        t_urlsafe_decode
    }
    fn setup(t: mode) -> map::hashmap<str, str> {
        let m = map::new_str_hash::<str>();
        m.insert("", "");
        alt t {
          t_decode | t_urlsafe_decode {
            m.insert("cGxlYXN1cmUu", "pleasure.");
            m.insert("bGVhc3VyZS4=",  "leasure.");
            m.insert("ZWFzdXJlLg==",   "easure.");
            m.insert("YXN1cmUu",        "asure.");
            m.insert("c3VyZS4=",         "sure.");
          }
          t_encode | t_urlsafe_encode {
            m.insert("pleasure.", "cGxlYXN1cmUu");
            m.insert("leasure.",  "bGVhc3VyZS4=");
            m.insert("easure.",   "ZWFzdXJlLg==");
            m.insert("asure.",        "YXN1cmUu");
            m.insert("sure.",         "c3VyZS4=");
          }
        }
        m
    }
    fn do_test(t: mode) {
        let b64 = mk();
        let m = setup(t);
        m.keys { |k|
            let expected = m.get(k);
            let actual = alt t {
              t_decode { from_bytes(b64.decode(bytes(k))) }
              t_encode { from_bytes(b64.encode(bytes(k))) }
              t_urlsafe_decode { from_bytes(b64.urlsafe_decode(bytes(k))) }
              t_urlsafe_encode { from_bytes(b64.urlsafe_encode(bytes(k))) }
            };
            #debug("expected: %s", expected);
            #debug("actual:   %s", actual);
            assert expected == actual;
        }
    }
    #[test]
    fn decode() { do_test(t_decode); }
    #[test]
    fn encode() { do_test(t_encode); }
    #[test]
    fn urlsafe_decode() { do_test(t_urlsafe_decode); }
    #[test]
    fn urlsafe_encode() { do_test(t_urlsafe_encode); }
}
<commit_msg>add function(s) to base64.rs<commit_after>//
// base64.rs - base64 implementation
//
// The Base64 Alphabet
//
//  Value Encoding  Value Encoding  Value Encoding  Value Encoding
//      0 A            17 R            34 i            51 z
//      1 B            18 S            35 j            52 0
//      2 C            19 T            36 k            53 1
//      3 D            20 U            37 l            54 2
//      4 E            21 V            38 m            55 3
//      5 F            22 W            39 n            56 4
//      6 G            23 X            40 o            57 5
//      7 H            24 Y            41 p            58 6
//      8 I            25 Z            42 q            59 7
//      9 J            26 a            43 r            60 8
//     10 K            27 b            44 s            61 9
//     11 L            28 c            45 t            62 +
//     12 M            29 d            46 u            63 /
//     13 N            30 e            47 v
//     14 O            31 f            48 w         (pad) =
//     15 P            32 g            49 x
//     16 Q            33 h            50 y
//
// The "URL and Filename safe" Base 64 Alphabet
//
// Value Encoding  Value Encoding  Value Encoding  Value Encoding
//     0 A            17 R            34 i            51 z
//     1 B            18 S            35 j            52 0
//     2 C            19 T            36 k            53 1
//     3 D            20 U            37 l            54 2
//     4 E            21 V            38 m            55 3
//     5 F            22 W            39 n            56 4
//     6 G            23 X            40 o            57 5
//     7 H            24 Y            41 p            58 6
//     8 I            25 Z            42 q            59 7
//     9 J            26 a            43 r            60 8
//    10 K            27 b            44 s            61 9
//    11 L            28 c            45 t            62 - (minus)
//    12 M            29 d            46 u            63 _ (underline)
//    13 N            30 e            47 v
//    14 O            31 f            48 w
//    15 P            32 g            49 x
//    16 Q            33 h            50 y         (pad) =
//

use std;

import std::map;

export base64, mk;

const padd: u8 = 61u8;

iface base64 {
    fn encode(src: [u8]) -> [u8];
    fn decode(src: [u8]) -> [u8];
    fn decode_w(src: [u8]) -> [u8];
    fn urlsafe_encode(src: [u8]) -> [u8];
    fn urlsafe_decode(src: [u8]) -> [u8];
    fn urlsafe_decode_w(src: [u8]) -> [u8];
}

fn mk() -> base64 {
    type _base64 = {table: [u8]};

    impl of base64 for _base64 {
        fn encode(src: [u8]) -> [u8] {
            b64encode(self.table + [43u8, 47u8], src)
        }
        fn decode(src: [u8]) -> [u8] {
            b64decode(self.table + [43u8, 47u8], src)
        }
        fn urlsafe_encode(src: [u8]) -> [u8] {
            b64encode(self.table + [45u8, 95u8], src)
        }
        fn urlsafe_decode(src: [u8]) -> [u8] {
            b64decode(self.table + [45u8, 95u8], src)
        }
        fn decode_w(src: [u8]) -> [u8] {
            b64decode_w(self.table + [43u8, 47u8], src)
        }
        fn urlsafe_decode_w(src: [u8]) -> [u8] {
            b64decode_w(self.table + [45u8, 95u8], src)
        }
    }

    let table = vec::init_elt_mut(62u, 0u8), i = 0u8;
    u8::range(65u8, 91u8)  { |j| table[i] = j; i += 1u8; }
    u8::range(97u8, 123u8) { |j| table[i] = j; i += 1u8; }
    u8::range(48u8, 58u8)  { |j| table[i] = j; i += 1u8; }

    {table: vec::from_mut(table)} as base64
}

fn b64idx(x: u8, y: u8, z: u8) -> u8 {
    if 65u8 <= x && x <= 90u8 { x - 65u8 }
    else if 97u8 <= x && x <= 122u8 { x - 97u8 + 26u8 }
    else if 48u8 <= x && x <= 57u8  { x - 48u8 + 52u8 }
    else if x == y { 62u8 }
    else if x == z { 63u8 }
    else { fail "malformed base64 string"; }
}

fn b64encode(table: [u8], src: [u8]) -> [u8] {
    let srclen = vec::len(src);
    let targ = if srclen % 3u == 0u {
        vec::init_elt_mut((srclen / 3u) * 4u, 0u8)
    } else {
        vec::init_elt_mut((srclen / 3u + 1u) * 4u, 0u8)
    };
    let input = vec::init_elt_mut(3u, 0u8);
    let output = vec::init_elt_mut(4u, 0u8);
    let curr = 0u, src_curr = 0u;

    while srclen > 2u {
        input[0] = src[src_curr];
        input[1] = src[src_curr + 1u];
        input[2] = src[src_curr + 2u];
        srclen -= 3u; src_curr += 3u;

        output[0] = input[0] >> 2u8;
        output[1] = ((input[0] &  3u8) << 4u8) | (input[1] >> 4u8);
        output[2] = ((input[1] & 15u8) << 2u8) | (input[2] >> 6u8);
        output[3] = input[2] & 63u8;

        targ[curr + 0u] = table[output[0]];
        targ[curr + 1u] = table[output[1]];
        targ[curr + 2u] = table[output[2]];
        targ[curr + 3u] = table[output[3]];
        curr += 4u;
    }

    if srclen != 0u {
        input[0] = 0u8; input[1] = 0u8; input[2] = 0u8;

        alt srclen {
          1u {input[0] = src[src_curr];}
          2u {input[0] = src[src_curr];
              input[1] = src[src_curr + 1u];}
          _  { }
        }

        output[0] = input[0] >> 2u8;
        output[1] = ((input[0] &  3u8) << 4u8) | (input[1] >> 4u8);
        output[2] = ((input[1] & 15u8) << 2u8) | (input[2] >> 6u8);

        targ[curr + 0u] = table[output[0]];
        targ[curr + 1u] = table[output[1]];
        targ[curr + 2u] = if srclen == 1u { padd } else { table[output[2]] };
        targ[curr + 3u] = padd;
    }

    vec::from_mut(targ)
}

fn b64decode(table: [u8], src: [u8]) -> [u8] {
    let srclen = vec::len(src);

    if srclen % 4u != 0u { fail "malformed base64 string"; }
    if srclen == 0u { ret []; }

    let input  = vec::init_elt_mut(4u, 0u8);
    let output = vec::init_elt_mut(4u, 0u8);
    let targ = if src[srclen - 2u] == padd {
        vec::init_elt_mut(srclen / 4u * 3u - 2u, 0u8)
    } else if src[srclen - 1u] == padd {
        vec::init_elt_mut(srclen / 4u * 3u - 1u, 0u8)
    } else {
        vec::init_elt_mut(srclen / 4u * 3u, 0u8)
    };
    let curr = 0u, src_curr = 0u;

    while srclen > 4u {
        input[0] = src[src_curr];
        input[1] = src[src_curr + 1u];
        input[2] = src[src_curr + 2u];
        input[3] = src[src_curr + 3u];
        srclen -= 4u; src_curr += 4u;

        output[0] = b64idx(input[0], table[62], table[63]);
        output[1] = b64idx(input[1], table[62], table[63]);
        output[2] = b64idx(input[2], table[62], table[63]);
        output[3] = b64idx(input[3], table[62], table[63]);

        targ[curr + 0u] = (output[0] << 2u8) | (output[1] >> 4u8);
        targ[curr + 1u] = ((output[1] & 15u8) << 4u8) | (output[2] >> 2u8);
        targ[curr + 2u] = ((output[2] &  3u8) << 6u8) | output[3];
        curr += 3u;
    }

    if srclen == 4u {
        input[0] = src[src_curr];
        input[1] = src[src_curr + 1u];
        input[2] = src[src_curr + 2u];
        input[3] = src[src_curr + 3u];

        output[0] = b64idx(input[0], table[62], table[63]);
        output[1] = b64idx(input[1], table[62], table[63]);

        targ[curr + 0u] = (output[0] << 2u8) | (output[1] >> 4u8);

        if input[2] == padd { ret vec::from_mut(targ); }

        output[2] = b64idx(input[2], table[62], table[63]);

        targ[curr + 1u] = ((output[1] & 15u8) << 4u8) | (output[2] >> 2u8);

        if input[3] == padd { ret vec::from_mut(targ); }

        output[3] = b64idx(input[3], table[62], table[63]);

        targ[curr + 2u] = ((output[2] & 3u8) << 6u8) | output[3];
    }

    vec::from_mut(targ)
}

fn b64decode_w(table: [u8], src: [u8]) -> [u8] {
    let srclen = vec::len(src);

    if srclen == 0u { ret []; }

    let targ = if src[srclen - 2u] == padd {
        vec::init_elt_mut(srclen / 4u * 3u - 2u, 0u8)
    } else if src[srclen - 1u] == padd {
        vec::init_elt_mut(srclen / 4u * 3u - 1u, 0u8)
    } else {
        vec::init_elt_mut(srclen / 4u * 3u, 0u8)
    };
    let end = false, i = 0, in = 0u8;
    let output = vec::init_elt_mut(4u, 0u8), outlen = 4;
    let src_curr = 0u, curr = 0u;

    while srclen > 0u && !end {
        i = 0;
        while i < 4 {
            if srclen == 0u {
                fail "malformed base64 string";
            }
            in = src[src_curr]; srclen -= 1u; src_curr += 1u;
            // ignore whitespace, tab, "\r", "\n"
            if in == 13u8 || in == 10u8 ||
               in == 32u8 || in == 9u8 {
                cont;
            }
            if in == padd && i >= 2 && srclen < 4u {
                if srclen > 0u && src[src_curr] != padd {
                    fail "malformed base64 string";
                }
                outlen = i; end = true;
                break;
            }
            output[i] = b64idx(in, table[62], table[63]);
            i += 1;
        }
        if outlen == 4 {
            targ[curr + 0u] = (output[0] << 2u8) | (output[1] >> 4u8);
            targ[curr + 1u] = ((output[1] & 15u8) << 4u8) | (output[2] >> 2u8);
            targ[curr + 2u] = ((output[2] &  3u8) << 6u8) | output[3];
            curr += 3u;
        } else if outlen == 3 {
            targ[curr + 0u] = (output[0] << 2u8) | (output[1] >> 4u8);
            targ[curr + 1u] = ((output[1] & 15u8) << 4u8) | (output[2] >> 2u8);
            curr += 2u;
        } else if outlen == 2 {
            targ[curr + 0u] = (output[0] << 2u8) | (output[1] >> 4u8);
            curr += 1u;
        }
    }

    vec::slice(vec::from_mut(targ), 0u, curr)
}

#[cfg(test)]
mod tests {
    import core::str::{from_bytes, bytes};
    enum mode {
        t_decode,
        t_encode,
        t_urlsafe_encode,
        t_urlsafe_decode,
        t_decode_w,
        t_urlsafe_decode_w,
    }
    fn setup(t: mode) -> map::hashmap<str, str> {
        let m = map::new_str_hash::<str>();
        m.insert("", "");
        alt t {
          t_decode | t_urlsafe_decode | t_decode_w | t_urlsafe_decode_w {
            m.insert("cGxlYXN1cmUu", "pleasure.");
            m.insert("bGVhc3VyZS4=",  "leasure.");
            m.insert("ZWFzdXJlLg==",   "easure.");
            m.insert("YXN1cmUu",        "asure.");
            m.insert("c3VyZS4=",         "sure.");
          }
           t_encode | t_urlsafe_encode {
            m.insert("pleasure.", "cGxlYXN1cmUu");
            m.insert("leasure.",  "bGVhc3VyZS4=");
            m.insert("easure.",   "ZWFzdXJlLg==");
            m.insert("asure.",        "YXN1cmUu");
            m.insert("sure.",         "c3VyZS4=");
          }
        }
        m
    }
    fn do_test(t: mode) {
        let b64 = mk();
        let m = setup(t);
        m.keys { |k|
            let expected = m.get(k);
            let actual = alt t {
              t_decode { from_bytes(b64.decode(bytes(k))) }
              t_encode { from_bytes(b64.encode(bytes(k))) }
              t_urlsafe_decode { from_bytes(b64.urlsafe_decode(bytes(k))) }
              t_urlsafe_encode { from_bytes(b64.urlsafe_encode(bytes(k))) }
              t_decode_w { from_bytes(b64.decode_w(bytes(k))) }
              t_urlsafe_decode_w { from_bytes(b64.urlsafe_decode_w(bytes(k))) }
            };
            #debug("  expected: %s", expected);
            #debug("  actual:   %s", actual);
            assert expected == actual;
        }
    }
    #[test]
    fn decode() { do_test(t_decode); }
    #[test]
    fn encode() { do_test(t_encode); }
    #[test]
    fn urlsafe_decode() { do_test(t_urlsafe_decode); }
    #[test]
    fn urlsafe_encode() { do_test(t_urlsafe_encode); }
    #[test]
    fn decode_w() { do_test(t_decode_w); }
    #[test]
    fn urlsafe_decode_w() { do_test(t_urlsafe_decode_w); }
}
<|endoftext|>


<commit_before>pub fn sing(start: u32, end: u32) -> String {
    let mut song = String::new();
    for i in (end..start+1).rev() {
        song.push_str(&verse(i));
        song.push_str("\n");
    }
    song.pop();
    song
}

pub fn verse(n: u32) -> String {
    if n == 0 {
        "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n".to_string()
    } else if n == 1 {
        "1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n".to_string()
    } else if n == 2 {
        "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n".to_string()
    } else {
        format!("{} bottles of beer on the wall, {} bottles of beer.\nTake one down and pass it around, {} bottles of beer on the wall.\n", n, n, n-1)
    }
}
<commit_msg>Make beer-song much more idiomatic<commit_after>pub fn sing(start: u32, end: u32) -> String {
    (end..start+1).rev().map(|x| verse(x)).collect::<Vec<_>>().join("\n")
}

pub fn verse(n: u32) -> String {
    match n {
        0 => "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n".to_string(),
        1 => "1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n".to_string(),
        2 => "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n".to_string(),
        _ => format!("{} bottles of beer on the wall, {} bottles of beer.\nTake one down and pass it around, {} bottles of beer on the wall.\n", n, n, n-1),
    }
}
<|endoftext|>